High Frequency Trading on Solana: Where the Latency Is

What HFT means on a chain that moves in discrete slots, why microsecond code tuning is usually wasted, and what actually decides your fill rate.

BoltTx Team··10 min read
solanahfthigh-frequency-tradinglatencytransaction-landingtrading-bot

High frequency trading on Solana is not the same activity as HFT on a traditional exchange, and copying the playbook from one to the other is how teams waste their first six months.

On a matching engine, latency is measured in microseconds and every one of them matters. On Solana, the chain moves in discrete slots. ★Being marginally faster than a competitor changes nothing if you both land in the same slot.★

What Actually Quantises

Solana produces blocks on a fixed cadence. Your transaction lands in slot N or slot N+1 — there is no partial credit for arriving earlier within the same slot.

This has a consequence people miss:

Halving your latency inside one slot  → ★may change nothing★
Shaving just enough to land a slot earlier → ★changes everything★

The second crosses a slot boundary. The first does not. ★Optimisation only pays where it moves you across a boundary★, which means the useful question is not "how fast am I" but "which slot do I land in."

So the first thing to measure is not milliseconds. It is slot distance:

const submitSlot = await connection.getSlot("processed");
const sig = await connection.sendRawTransaction(raw, {
  skipPreflight: true,
  maxRetries: 0,
});

const { value } = await connection.getSignatureStatuses([sig], {
  searchTransactionHistory: true,
});

log({
  submitSlot,
  landSlot: value[0]?.slot ?? null,
  slotGap: value[0]?.slot ? value[0].slot - submitSlot : null,
});

If you already measure this and the gap is your problem, a free BoltTx key is one line to test against.

Where the Time Actually Goes

For a Solana HFT loop, the budget breaks down roughly like this:

Segment Order of magnitude Yours to fix
Signing (ed25519) microseconds ★already negligible★
Instruction building microseconds already negligible
Serialization microseconds already negligible
★Network to endpoint★ ★milliseconds★ ★yes — geography, keep-alive★
★Endpoint to validator★ ★milliseconds★ ★provider choice★
Waiting for inclusion ★slots★ fee, retry, routing

★The three items at the top add up to less than one percent of the total.★ Rewriting your bot in a faster language optimises those three.

This is the single most common misallocation in Solana HFT: teams port a TypeScript bot to Rust, gain a few hundred microseconds, and land in exactly the same slot they did before.

Rust is a reasonable choice for other reasons — predictable memory behaviour, no garbage collection pauses that occasionally cost you a whole slot. ★But "Rust is faster" is not, by itself, a latency argument on Solana.★

The Optimisations That Do Cross Boundaries

Geography. Network round trip is often the largest single term you control. Submitting from a machine near your endpoint versus across an ocean is worth more than every code optimisation combined.

Connection reuse. A cold TLS connection means a full handshake before your bytes move. Eliminate it:

import { Agent } from "undici";

// Warm pool, no handshake in the hot path.
const agent = new Agent({
  keepAliveTimeout: 60_000,
  connections: 16,
});

On a reused connection, HTTPS costs essentially the same as plain HTTP. ★For a bot sending continuously, the handshake is latency you pay on every transaction for no reason.★

Nothing on the network in the hot path. Every round trip between deciding and submitting is a slot you may lose:

// Refresh in the background. Never fetch blockhash on demand.
let cached = await connection.getLatestBlockhash("confirmed");
setInterval(async () => {
  cached = await connection.getLatestBlockhash("confirmed");
}, 5_000);

Priority fees derived from live conditions. A hardcoded microLamports value is wrong in both directions:

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

const recent = await connection.getRecentPrioritizationFees({
  lockedWritableAccounts: writableAccounts,   // ★fees are per-account★
});
const fees = recent.map((r) => r.prioritizationFee).sort((a, b) => a - b);
const median = fees[Math.floor(fees.length / 2)] ?? 0;

instructions.unshift(
  ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: Math.max(median * 2, 5_000),
  }),
  ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
);

Throughput Is a Different Problem

HFT usually means high volume as well as low latency, and volume runs into constraints that latency work does not touch.

Rate limits. Hundreds of reads per second hits tiers quickly. Batch aggressively:

// One call per 100 accounts, not one per account.
const infos = await connection.getMultipleAccountsInfo(pubkeys);

Blockhash reuse across a batch. Signing many transactions against one blockhash means the tail expires. Refresh partway through, or sign against the cached value that is being refreshed continuously.

Nonce accounts for long-lived transactions. If a transaction must stay valid beyond the roughly 150-block window, a durable nonce replaces the recent blockhash and removes the expiry entirely. It costs an extra account and an advanceNonce instruction.

Account write contention. Two of your own transactions writing the same account in one slot serialise against each other. At high volume you can compete with yourself.

Rust, TypeScript, and What Actually Differs

Since this is the most argued-about decision, worth being specific.

★Raw execution speed is not the differentiator★, because the compute-bound portion of an HFT loop is already negligible against network time.

What does differ:

Garbage collection. A Node.js major GC pause can occasionally exceed a slot. Rare, but it happens at the worst time — under load, when your queues are deepest.

Tail predictability. Rust's timing distribution is tighter. For a strategy living in the tail rather than the median, that consistency is the real argument.

Connection control. Lower-level HTTP control makes it easier to guarantee no handshake in the hot path.

★If your TypeScript bot lands within two slots consistently, rewriting it in Rust will not improve your fill rate.★ Measure the slot distribution before committing to a rewrite.

What Landing Looks Like

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

★For HFT the number that matters is the shape of the tail, not the median.★ A path that is usually fast but occasionally slow will miss exactly the contested opportunities you built the system for.

What to Instrument

log({
  decideAt,                          // strategy decision
  submitAt,                          // sendRawTransaction returned
  submitSlot,
  landSlot,
  slotGap: landSlot - submitSlot,    // ★the metric★
  blockhashAge: submitAt - blockhashFetchedAt,
  feeMicroLamports,
  congestionBucket,                  // busy vs calm
});

Bucket by congestion and compare distributions rather than averages. ★A slot gap that holds when calm and widens under load points at routing★, which is the one item you cannot fix in application code.

Where BoltTx Fits

We work on the endpoint-to-validator segment. Not indexing, not streaming, not parsed history.

Submissions route through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure, so a transaction is not observable in transit before it lands. 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:

// Pick the region closest to where your bot runs
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

FAQ

What counts as high frequency trading on Solana? Strategies where the opportunity closes within a slot or two — arbitrage, sniping, liquidations, market making with tight quotes. The defining constraint is landing before the state you traded on changes.

Is Solana fast enough for HFT? For strategies operating at slot granularity, yes. For microsecond-level strategies ported from traditional venues, no — the chain quantises to slots, so sub-slot speed advantages do not translate.

Should I write my Solana HFT bot in Rust or TypeScript? Raw speed is rarely the differentiator, since compute time is negligible against network time. Rust helps with garbage collection pauses and tail predictability. If your TypeScript bot already lands within two slots consistently, a rewrite will not change fill rate.

Why doesn't making my bot faster improve my fill rate? Because Solana lands transactions in discrete slots. Shaving milliseconds only matters if it moves you across a slot boundary. Measure slot distance rather than milliseconds to see whether an optimisation is doing anything.

What is the biggest latency factor for Solana HFT? Usually network geography and the path from your endpoint to a block producer. Signing, serialization, and instruction building together account for a negligible share of total time.

How do I reduce Solana transaction latency for HFT? Reuse connections, submit to a nearby endpoint, keep a background-refreshed blockhash so nothing hits the network between decision and submission, and derive priority fees from live conditions.

Does colocation help on Solana? Being close to your submission endpoint helps. Beyond that, validator scheduling and stake-weighted acceptance matter more than raw physical proximity to any single machine.

What is a good slot gap for an HFT bot? Landing within two slots consistently is a reasonable target. Consistency matters more than the median, since a path with an occasional long tail misses exactly the contested opportunities.

How do I handle rate limits at high volume? Batch with getMultipleAccounts instead of looping getAccountInfo, replace polling with subscriptions where possible, and filter program subscriptions server-side. Read volume and send behaviour are separate constraints.

Should I use durable nonces for HFT? Only if transactions need to stay valid beyond the roughly 150-block blockhash window. For fast-turnaround trading, a background-refreshed recent blockhash is simpler and adds no extra account or instruction.

Can my own transactions compete with each other? Yes. Two transactions writing the same account in one slot serialise against each other. At high volume this is a real effect, and it argues for spreading writes across accounts where the strategy allows.

Does garbage collection actually cost me trades? Occasionally, and at the worst time. A major GC pause can exceed a slot, and it is most likely under load when your queues are deepest — which is when opportunities cluster.

What should I measure to know if my HFT setup is working? Slot distance from submit to land, bucketed by congestion, tracked as a distribution. Averages hide the tail, and the tail is where a latency-sensitive strategy actually lives.

Is market making viable on Solana? Quote updates and cancellations are on-chain transactions with the same landing constraints as any other. The economics depend on how often you need to requote against how reliably those updates land.

Why does my bot perform well in backtests but poorly live? Backtests usually assume you got the price you saw. Live, your transaction lands one or more slots later against changed state, and during congestion that gap widens exactly when the opportunities appear.

How much does compute unit limit matter for HFT? It does not affect speed, but an underestimate fails transactions that would otherwise have landed, and the default charges you against a much higher figure. Simulate during development and set slightly above real usage.

Back to all posts