Solana Liquidation Bots: Winning a Race You Cannot Predict

Liquidations are a race with no starting gun. How to watch health factors, when to submit, and why the winners land first rather than detect first.

BoltTx Team··10 min read
solanaliquidation-botdefitransaction-landingtrading-botmev

Liquidations look like the cleanest opportunity in DeFi. A position becomes eligible, you close it, you take the bonus. The rules are public and the profit is defined in advance.

That is exactly why they are hard. Everyone can see the same positions approaching the same thresholds, and the entire competition collapses into who lands first.

The Race Has No Starting Gun

Most trading opportunities begin with an event: a launch, a large swap, a price print. A liquidation has no such event — a position crosses a threshold because a price moved somewhere else, and nothing on chain announces it.

So the race starts at different moments for different bots, depending on how each one is watching. A bot polling every five seconds discovers it late. A bot watching the oracle discovers it at the moment the price updates.

★But discovering first only matters if you also land first.★ Two bots that spot the same position at the same instant are then in a pure submission race, and that is decided by fee, retry behaviour, and routing.

Two Ways to Watch

Poll health factors. Read every position with getProgramAccounts on a timer, compute health, act on anything below threshold. Simple, and expensive at scale — hundreds of accounts on a short interval burns quota fast.

// Batch, do not loop. One call for up to 100 accounts.
const accounts = await connection.getMultipleAccountsInfo(positionPubkeys);
const atRisk = accounts
  .map((acc, i) => ({ pubkey: positionPubkeys[i], state: deserialize(acc.data) }))
  .filter((p) => healthFactor(p.state) < 1.05); // watch the approach, not just the breach

★Filter on approaching the threshold, not on crossing it.★ By the time health is below 1.0 in your poll, the opportunity is already contested. Tracking positions at 1.05 gives you time to prepare.

Watch the price source. Positions become liquidatable because a price moved. If you watch the oracle update rather than the position, you learn about the change at the same moment the protocol does.

connection.onAccountChange(
  oracleAccount,
  (info) => {
    const price = parsePrice(info.data);
    // Recompute health for positions you already know are near the edge.
    const nowEligible = watchlist.filter((p) => healthAt(p, price) < 1.0);
    nowEligible.forEach(submitLiquidation);
  },
  "processed",
);

This is the meaningful optimisation: ★pre-compute which positions become eligible at which price, then act on the price update rather than rescanning everything.★

If your detection is already fast and the problem is landing first, a free BoltTx key is one line to test against.

Why Liquidations Congest

Liquidation opportunities cluster. A sharp price move makes many positions eligible at once, and every bot watching reacts in the same second.

This produces the familiar trap: your bot performs well in testing and loses every contested liquidation in production. Testing happens when the network is calm; ★liquidations happen precisely when it is not.★

Three things decide the outcome at that moment:

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

// Fees are contested per account. Every competing bot is writing
// to the same position and vault accounts you are.
const recent = await connection.getRecentPrioritizationFees({
  lockedWritableAccounts: [positionAccount, vaultAccount],
});
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({
    // A liquidation has a known payoff. Size the fee against it
    // rather than against a fixed constant.
    microLamports: Math.max(median * 3, 10_000),
  }),
  ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
);

Plus continuous retry until the blockhash expires, and a submission path that holds up when block space is contested.

Sizing the Fee Against the Bonus

Liquidations have a defined payoff, which means the fee decision is arithmetic rather than guesswork.

// Liquidation bonus in lamports, minus the fees you will pay.
const bonusLamports = collateralValue * bonusRate;
const baseFee = 5_000;                      // signature cost
const priorityCost = (cuPrice * cuLimit) / 1_000_000;

const netIfWon = bonusLamports - baseFee - priorityCost;
const costIfLost = baseFee;                 // ★a losing race still pays this★

★The last line is what most bots ignore.★ Losing a liquidation race is not free — you paid a base fee for a transaction that landed and failed, or that never landed at all.

If you contest ten liquidations and win two, your fee cost is ten attempts against two payoffs. A bot with a 20% win rate needs each win to cover five attempts, which changes what fee is rational.

Failure Modes Specific to Liquidations

Someone else got there first. Your transaction lands and reverts because the position is already healthy. You paid the base fee for nothing. This is the normal cost of competing.

The position healed. The price moved back before your transaction landed. Same outcome, different cause — and it argues for tighter latency rather than higher fees.

Compute budget exhausted. Liquidations often touch many accounts and can be compute-heavy. ★An underestimated CU limit fails a transaction that would otherwise have won★, which is the most avoidable loss on this list. Simulate against a realistic position during development.

Stale oracle price. You computed eligibility from a price the protocol no longer accepts. The transaction reverts on the protocol's own staleness check.

What Landing Looks Like

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

In a contested liquidation, the difference between landing at slot 2 and slot 5 is usually the difference between the bonus and a wasted base fee.

★During volatility that number stretches★ — most transactions are unaffected while a minority take noticeably longer. Liquidations live in that minority, because the price moves that create them are the same events that congest the network.

Measuring the Right Thing

Win rate alone does not tell you where you are losing.

log({
  position,
  eligibleSlot,                 // when it crossed the threshold
  detectSlot,                   // when you noticed
  submitSlot,
  landSlot,
  detectLag: detectSlot - eligibleSlot,   // ★watching problem★
  landLag: landSlot - submitSlot,         // ★submission problem★
  outcome,                                // won / reverted / never landed
});

★detectLag and landLag point at completely different fixes.★ A large detect lag means change how you watch. A large land lag means fee, retry, or routing. Optimising the wrong one is how teams spend months without moving their win rate.

Where BoltTx Fits

We handle submission. Not oracle feeds, not position indexing, not protocol integration.

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 — which matters when your transaction reveals which position you are about to take.

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:

// Detection stays where it is; only the send endpoint changes.
const sender = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

Then log landLag across a few contested liquidations and compare.

FAQ

How does a Solana liquidation bot work? It watches lending positions for health factors approaching the liquidation threshold, then submits a liquidation transaction when one becomes eligible. The protocol pays a bonus for closing the position, and multiple bots compete for the same opportunity.

Why does my liquidation bot keep losing the race? Usually submission rather than detection. If you spot eligible positions quickly but land three or more slots later, the loss is in fee, retry behaviour, or routing under the congestion that price moves create.

Should I poll positions or watch the oracle? Watching the price source is faster, because positions become eligible as a consequence of a price update. Pre-compute which positions flip at which price, then react to the oracle rather than rescanning everything.

How do I monitor many positions without hitting rate limits? Batch with getMultipleAccounts rather than looping getAccountInfo. Track positions approaching the threshold rather than scanning everything, and consider a subscription on the oracle instead of polling positions.

What priority fee should a liquidation bot use? Size it against the known bonus rather than a fixed constant. Factor in that losing races still costs a base fee, so your effective cost per win is the fee multiplied by your attempt count.

Why did my liquidation transaction revert? Common causes: another bot got there first and the position is now healthy, the price recovered before you landed, the compute budget was too low, or the oracle price you used was considered stale by the protocol.

How much compute budget does a liquidation need? More than most instructions, since liquidations touch many accounts. Simulate against a realistic position during development and set the limit slightly above the result — an underestimate fails a transaction that would otherwise have won.

Is liquidation on Solana still profitable? On competitive protocols the margins are thin and decided by execution. Less-watched protocols and unusual collateral types leave more room. Either way the deciding factor is landing speed rather than strategy.

Do I need MEV protection for liquidations? A private submission path helps, since your transaction reveals which position you are about to close. It does not remove the competition, because other bots can see the same eligible position independently.

What health factor should trigger my bot? Track positions before they cross the threshold, not at the moment they do. Watching from around 1.05 gives you time to prepare and warm the path; waiting for below 1.0 means you start the race late.

Why do liquidations cluster together? Because a single price move makes many positions eligible at once. That same move congests the network, which is why liquidation opportunities and difficult submission conditions arrive together.

Can I run a liquidation bot on a free RPC tier? Monitoring might fit for a small watchlist. Competing on submission during a volatility event is the worst case for shared endpoints, since they are busiest at exactly those moments.

How do I know if detection or submission is my bottleneck? Log the slot where the position became eligible, where you detected it, and where your transaction landed. Detect lag and land lag point at completely different fixes.

Should I liquidate partially or fully? Depends on protocol rules and your capital. Partial liquidations reduce capital requirements and may face less competition, but earn proportionally less per transaction while paying the same base fee.

What happens if two bots liquidate the same position? The first to land succeeds. The second reverts because the position is no longer eligible, and still pays the base fee for a transaction that reached the chain and failed.

← Back to all posts