Liquidation Bot
How to build a liquidation bot
Building a Liquidation Bot on Kairos
Kairos liquidations are unusually simple to keep. There is no flash loan, no collateral auction, no debt to assume, and no token you are forced to hold. A liquidator is a pure keeper: it spots a position whose margin has run out, calls one function, and walks away with a bounty that was escrowed up front. This guide explains the mechanism, then walks through building a bot end to end.
TLDR — Call liquidateSwap(swapId) on an undercollateralized swap. You pay gas; the protocol pays you a prefunded bounty in the market's settlement token (e.g. USDC). You never take on the position, inventory, or price risk.
1. How liquidation works
What a swap looks like
Each Kairos market is an onchain interest rate swap. A buyer posts collateral to take a fixed-vs-floating position for a fixed term; the LP pool backs the other side. As the reference rate moves, one side accrues an obligation to the other. That obligation is settled from collateral both sides posted at entry.
A position can close three ways:
Closure
How
closureType
Expiry
Anyone calls makePayment after the term ends
0
Early exit
Buyer exits before expiry (if the market allows it)
1
Liquidation
Anyone calls liquidateSwap while the position is underwater
2
Liquidation exists for the case where the rate moves so far against one side, before expiry, that its posted collateral can no longer cover what it owes. Rather than let the position drift further underwater, anyone may force-settle it early.
When is a swap liquidatable?
A position is liquidatable when all of these hold:
It is not settled. Closed positions are skipped.
It has not expired. If
block.timestamp >= entryTimestamp + swapTerm, it is not liquidatable — expired positions are settled normally viamakePayment, not liquidated.Accrued obligation has consumed the owing side's collateral.
The third condition uses accrued payments only — the mark-to-market obligation up to now, derived from the move in the reference-rate index since entry. Projected future payments are not counted.
The threshold differs by which side owes:
In plain terms: the buyer becomes liquidatable once its accrued obligation reaches the collateral it posted. The pool side becomes liquidatable just before its backing is fully consumed, so there is always enough left to pay the liquidator.
Where the bounty comes from
The liquidator's reward is always a liquidationIncentive (a WAD percentage set per market) slice of the owing side's collateral — but the source depends on which side is liquidated.
Buyer-side liquidation — paid from the buyer's prefunded bounty. When the swap is created, the buyer prefunds a bounty inside buySwap:
It is stored on the position as swap.liquidationBounty and kept out of the bucket accounting so it cannot be confused with margin. If the buyer is liquidated, this prefunded amount is paid to whoever called liquidateSwap.
Pool-side liquidation — paid from the pool's collateral. When the pool is the owing side, the buyer's prefunded bounty is not used — it is returned to the buyer in full. Instead the reward is computed fresh from the pool's backing at liquidation time and carved out of it:
In both cases the reward is already sitting in the contract before your bot shows up — either as the buyer's prefunded bounty or as part of the pool's backing. You are never fronting it.
Why it matters for a bot: your expected payout is liquidationIncentive × (collateral of whichever side is underwater). For a buyer-side liquidation that is the buyer's prefunded liquidationBounty; for a pool-side liquidation it is a slice of poolCollateralBacking. Size your gas-vs-reward check against the correct side.
What happens on liquidation
liquidateSwap updates the oracle index, re-checks eligibility, force-settles the position, and pays out — strictly checks-effects-interactions, with external transfers last.
If the buyer is liquidated:
Liquidator receives the prefunded
swap.liquidationBounty.The buyer's entire
collateralBalanceis credited to the pool.The buyer receives nothing back.
If the pool is liquidated:
Liquidator receives
poolCollateralBacking * liquidationIncentive / WAD.The buyer receives the remaining pool backing, plus their own original collateral, plus their own prefunded bounty back.
Either way the position is marked settled, removed from its bucket, and the bounty is sent to msg.sender with a direct safeTransfer in the market's swapToken:
Events to index
Two events fire on every liquidation:
A bot can confirm its own fills by watching SwapLiquidated for its address, or filter SwapClosed on closureType == 2.
2. How to build a liquidation bot
A liquidation bot is a loop with four stages: discover open positions, filter to likely candidates, confirm onchain, and submit.
There is no public onchain isLiquidatable view. Utils.isSwapLiquidatable is a library function over storage references — it is not externally callable, and SwapCore does not re-expose it. The robust way to confirm eligibility is to simulate liquidateSwap with eth_call: if the position is not liquidatable, it reverts with E507. Use off-chain math only to cheaply pre-filter candidates before simulating.
Step 1 — Discover open positions
There is no onchain array of all swap IDs. Three discovery sources, easiest first:
A. The indexer (recommended). Kairos runs a Ponder indexer exposing a GraphQL API (default dev endpoint https://idxdev.kairosswap.com/graphql). The swaps table carries a status field ("open" | "expired" | "earlyExit" | "liquidated") and every entry field you need for the math:
B. The onchain expiry queue. Each market keeps an append-only queue of swap IDs with a pointer to the earliest unsettled one:
Walk [pointer, length) to enumerate live swaps without the indexer. (The queue is ordered by entry for expiry settlement, not pruned of unexpired-but-liquidatable entries, so you still filter yourself.)
C. SwapCreated event logs. Backfill historical swaps directly from chain if you do not want to depend on the indexer.
Step 2 — Pre-filter off-chain
For each open swap, drop anything that cannot be liquidated, cheaply:
Expired? If
now >= entryTimestamp + swapTerm, skip — that is amakePayment, not a liquidation.Estimate the obligation. Read the live reference index (
getFreshIndex(marketId)), derive the floating rate fromentryFloatingIndexversus the live index over the elapsed time, and compare accrued fixed vs floating legs against the owing side's collateral. Treat this only as a ranking heuristic — the contract's math is authoritative.
A lightweight proxy for "how close to the edge" is getSwapNetAmount(swapId), which returns the current net settlement amount and direction.
Step 3 — Confirm with a simulation
For each surviving candidate, simulate the real call. If it would succeed, it is liquidatable right now.
Step 4 — Submit
When the simulation passes, send the transaction. The call takes only the swapId — no amount, no token, no approval.
Putting it together
Operational notes
Liquidation is permissionless and competitive. liquidateSwap has no whitelist — anyone can call it. Expect other keepers. Win on latency (poll frequently, simulate fast) and gas strategy, not on privileged access.
Idempotency / races. Once a swap is liquidated it is
settled; a second call reverts. Always rely on the pre-sendsimulateContractso you do not burn gas on a position someone else just took.Keep the index fresh.
liquidateSwapupdates the oracle index itself before checking, so you do not have to. But your off-chain estimate uses the last snapshot — a position can cross the threshold the instant a new index lands. Re-simulate close to send time.Watch both sides. A market has a buyer side and a pool side, and either can be the one that gets liquidated. Your math must handle
rateType(BUY_FIXED= buyer pays fixed;BUY_FLOATING= buyer receives fixed), which flips who owes.Gas vs bounty. The bounty is
liquidationIncentive(a WAD percentage set per market) of the owing side's collateral. Before sending, confirm the bounty exceeds your gas cost — small positions on expensive chains may not be worth it.
3. The benefits — and the absence of collateral price risk
Kairos's liquidation model is deliberately a trigger, not a takeover. That removes the risks that make liquidation bots elsewhere capital-intensive and dangerous.
You provide gas, nothing else
There is no transferFrom anywhere in the liquidation path. You do not deposit collateral, you do not need token approvals, and you do not need a flash loan to fund a repayment. The bounty is already escrowed inside the contract — you call one function and receive it. The entire capital requirement is the gas for the transaction.
You never hold the position or any inventory
In an AMM or lending-market liquidation you typically buy the collateral, assume the debt, or take the seized asset onto your balance sheet — and then you have to sell it, exposing you to slippage and price moves in the window before you do. On Kairos you become a counterparty to nothing. liquidateSwap force-settles the position in place: collateral is routed between the existing buyer and pool, the position is marked settled, and you receive a fixed bounty. You hold no swap, no leg, no seized collateral.
No collateral price risk
Because you never acquire an asset, there is no asset to mark against the market. Your payout is the prefunded bounty, denominated in the market's swapToken — typically a stablecoin like USDC. The only "price exposure" you ever have is whatever you choose by holding that token afterward. There is:
No volatile collateral to offload before the price moves against you.
No debt to assume and refinance.
No flash-loan leg that can fail and strand you.
No inventory carried between blocks.
Net effect: the worst case for a Kairos liquidator is a reverted transaction (you lose only gas, and the pre-send simulation prevents most of those). This makes the role well suited to a lightweight keeper that optimizes purely for latency and gas, without requiring a treasury, hedging, or inventory management.
Why the protocol wants you there
Liquidations keep the protocol solvent: they close out underwater positions before the obligation exceeds posted collateral, protecting the counterparty (buyer or LP pool) from being left with an unbacked claim. The prefunded bounty exists precisely to make sure that work is always profitable to perform and always available to anyone — no permissions, no capital, no price risk.
Quick reference
Entry point
liquidateSwap(bytes32 swapId) — permissionless, gas only
Eligibility
not settled, not expired, accrued obligation ≥ owing side's collateral
Reward
prefunded liquidationBounty (buyer-side) or poolBacking × incentive (pool-side), paid in swapToken
Confirm before sending
eth_call simulate liquidateSwap; reverts E507 if not liquidatable
Discover positions
indexer GraphQL swaps(status:"open"), or onchain expiry queue, or SwapCreated logs
Events
SwapLiquidated, SwapClosed (closureType=2)
Risk
gas on a reverted tx only — no collateral, inventory, or price risk
Last updated