Automated liquidation bot for the Uncap Protocol on Starknet. Runs as a Cloudflare Worker on a per-minute cron schedule, monitoring and liquidating undercollateralized troves.
There are two versions of the bot, each on its own branch:
- Collateral: WWBTC (115% MCR)
- JIT liquidation: Supported via LiquidationsKeeper (2-hop swap: WBTC -> USDC -> USDU)
- Fallback: Redistribution when JIT fails (configurable)
- Account config: Single
LIQUIDATOR_ADDRESS/LIQUIDATOR_PRIVATE_KEY - Cloudflare Worker name:
liquidation
v2 (add_collaterals branch)
- Collaterals: WWBTC, TBTC, SOLVBTC
- JIT liquidation: Supported via a new LiquidationsKeeper contract with multi-hop swap routes
- WWBTC: WBTC -> USDC -> USDU (2 hops)
- TBTC: TBTC -> WBTC -> USDC -> USDU (3 hops)
- SOLVBTC: SOLVBTC -> WBTC -> USDC -> USDU (3 hops)
- Fallback: Redistribution when JIT fails (configurable)
- Account config: Network-specific credentials (
LIQUIDATOR_ADDRESS_MAINNET/_SEPOLIA) - Cloudflare Worker name:
liquidation-v2
v1 (main) |
v2 (add_collaterals) |
|
|---|---|---|
| Collaterals | WWBTC | WWBTC, TBTC, SOLVBTC |
| JIT swap hops | 2 | 2 or 3 (depends on collateral) |
| LiquidationsKeeper contract | v1 | v2 (takes troveManager, stabilityPool, swap route) |
| Unwrapping | WWBTC -> WBTC | WWBTC -> WBTC (TBTC/SOLVBTC don't need unwrapping) |
| Account config | Single key pair | Network-specific key pairs |
Every minute the Cloudflare Worker cron trigger fires and the bot runs through these steps:
- Discover — queries a GraphQL indexer for trove candidates that look close to liquidation
- Price — fetches fresh on-chain prices from the PriceFeed contract
- Validate — reads latest on-chain debt/collateral for at-risk troves and computes their collateral ratio (CR)
- Batch — groups liquidatable troves (CR < MCR) by collateral type, packs them into batches of max 20 using a greedy algorithm that maximizes Stability Pool usage
- Execute — submits each batch using the best available liquidation strategy (see below)
- Unwrap — converts wrapped collateral rewards back to underlying tokens (WWBTC -> WBTC)
The bot picks the best strategy for each batch using a three-tier hierarchy:
Used when the Stability Pool (SP) has enough USDU to cover the entire batch debt.
TroveManager.batch_liquidate_troves(troveIds)
-> SP USDU pays off debt
-> SP depositors receive collateral at ~5% discount
Used when the SP doesn't have enough USDU. The bot flash-borrows the deficit and temporarily tops up the SP.
LiquidationsKeeper.liquidate(jitAmount, troveIds, usduUsdcPoolKey, usdcWbtcPoolKey)
-> Flash borrow USDU deficit via Ekubo
-> Deposit into SP temporarily
-> Liquidate using full SP balance
-> Swap WBTC back to USDU via Ekubo (WBTC -> USDC -> USDU)
-> Repay flash loan
-> Profit stays in the LiquidationsKeeper contract
Used when JIT fails and ENABLE_REDISTRIBUTIONS is set to true.
TroveManager.batch_liquidate_troves(troveIds)
-> Uses whatever SP USDU is available
-> Remaining debt + collateral is redistributed to active borrowers
Is SP sufficient for full batch debt?
├── YES -> Tier 1: Stability Pool liquidation
└── NO -> Tier 2: JIT liquidation
├── Success -> Done (profit in keeper contract)
└── Fail
├── ENABLE_REDISTRIBUTIONS=true -> Tier 3: Redistribution
└── ENABLE_REDISTRIBUTIONS=false -> Skip batch
- Node.js (v18+)
- pnpm
- A Cloudflare account (for deployment)
- A funded Starknet account (ETH for gas)
- A Starknet RPC endpoint (e.g. Alchemy, Infura, Blast)
pnpm installCopy the example file and fill in your values:
cp .dev.vars.example .dev.vars| Variable | Description |
|---|---|
GRAPHQL_ENDPOINT |
URL of the GraphQL indexer |
NETWORK |
sepolia or mainnet |
NODE_URL |
Starknet RPC endpoint (e.g. Alchemy URL with API key) |
LIQUIDATOR_ADDRESS |
Your Starknet account address |
LIQUIDATOR_PRIVATE_KEY |
Your Starknet private key |
ENABLE_REDISTRIBUTIONS |
true / false — fallback to redistribution if JIT fails |
Start the worker in dev mode:
pnpm devIn another terminal, trigger the cron manually:
curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"Store secrets in Cloudflare (they're injected as env vars at runtime). You only need to do this once, or when rotating keys.
wrangler secret put GRAPHQL_ENDPOINT
wrangler secret put NETWORK
wrangler secret put NODE_URL
wrangler secret put LIQUIDATOR_ADDRESS
wrangler secret put LIQUIDATOR_PRIVATE_KEY
wrangler secret put ENABLE_REDISTRIBUTIONSpnpm deployThis runs wrangler deploy, which uploads the worker and activates the cron trigger (* * * * * — every minute).
Cloudflare Observability is enabled in wrangler.jsonc. View logs in the Cloudflare dashboard under Workers & Pages -> your worker -> Logs.
Cron Trigger (every 1 min)
|
v
Indexer Service -- GraphQL query for liquidatable candidates
|
v
Price Service -- Fetch fresh on-chain prices per collateral
|
v
Validator Service -- On-chain validation, filter by CR < MCR
|
v
Calculator Service -- Greedy batch packing (SP batches + JIT batches)
|
v
Executor Service -- Submit transactions (SP / JIT / redistribution)
|
v
Unwrap Rewards -- WWBTC -> WBTC (if applicable)
| Component | File | Role |
|---|---|---|
| Worker entry | src/worker.ts |
Cloudflare Worker cron handler |
| Orchestrator | src/run-liquidations.ts |
Coordinates all steps |
| Indexer | src/services/indexer.ts |
GraphQL candidate discovery |
| Prices | src/services/prices.ts |
On-chain price fetching |
| Validator | src/services/validator.ts |
On-chain trove validation |
| Calculator | src/services/calculator.ts |
Greedy batch packing algorithm |
| Executor | src/services/executor.ts |
Transaction execution + unwrapping |
| Collaterals | src/collaterals.ts |
Collateral config, MCR, contract addresses |
| Pool config | src/pool-config.ts |
Ekubo DEX pool parameters for JIT swaps |
If you modify GraphQL queries in src/graphql/documents.ts, regenerate types:
pnpm codegen- Fresh on-chain data — always validates prices, debt, and collateral before executing
- Risk buffer — only fetches on-chain data for troves within 10% of MCR (saves RPC calls)
- Error isolation — a failed batch does not stop the rest of the run
- JIT fallback — configurable fallback to redistribution if JIT fails
- Automatic unwrapping — WWBTC rewards are unwrapped to WBTC