Skip to main content

Liquidation Streaming

Stream real-time liquidation opportunities from DeFi lending protocols with health factor monitoring and profit calculations.

Quick Start

Connect to the liquidation WebSocket endpoint:

wss://api.axol.io/api/v1/defi/liquidations/stream?token=YOUR_JWT_TOKEN

Supported Protocols

ProtocolChainEndpoint Parameter
Aave V3Ethereum, Polygon, Arbitrumprotocol=aave_v3
Aave V2Ethereumprotocol=aave_v2
Compound V3Ethereum, Polygon, Arbitrumprotocol=compound_v3
Compound V2Ethereumprotocol=compound_v2
MorphoEthereumprotocol=morpho
SparkEthereumprotocol=spark

Connection

wss://api.axol.io/api/v1/defi/liquidations/stream?token=JWT&chain=ethereum&protocol=aave_v3

Query Parameters

ParameterTypeDefaultDescription
tokenstringRequiredJWT authentication token
chainstringethereumTarget chain
protocolstring-Filter by protocol (optional)
min_profitfloat0Minimum profit USD
health_thresholdfloat1.1Max health factor to stream

Message Types

Position Update

Real-time updates when a position's health factor changes:

{
"type": "position_update",
"data": {
"user": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb5",
"protocol": "aave_v3",
"chain": "ethereum",
"health_factor": 1.05,
"health_status": "AT_RISK",
"collateral": {
"total_usd": 150000,
"assets": [
{
"token": "WETH",
"amount": "50.0",
"value_usd": 150000,
"liquidation_threshold": 0.825
}
]
},
"debt": {
"total_usd": 120000,
"assets": [
{
"token": "USDC",
"amount": "120000",
"value_usd": 120000
}
]
},
"liquidation_opportunity": {
"max_debt_to_cover": "60000",
"debt_token": "USDC",
"collateral_to_receive": "20.0",
"collateral_token": "WETH",
"estimated_profit_usd": 3000,
"liquidation_bonus": 0.05
},
"price_drop_to_liquidation": -3.2
},
"timestamp": "2026-01-15T10:30:00.123Z"
}

Liquidation Alert

Immediate alert when a position becomes liquidatable:

{
"type": "liquidation_alert",
"priority": "high",
"data": {
"user": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb5",
"protocol": "aave_v3",
"chain": "ethereum",
"health_factor": 0.98,
"health_status": "LIQUIDATABLE",
"liquidation_opportunity": {
"max_debt_to_cover": "60000",
"debt_token": "USDC",
"collateral_to_receive": "20.5",
"collateral_token": "WETH",
"estimated_profit_usd": 4500,
"liquidation_bonus": 0.05,
"gas_estimate": 350000,
"net_profit_usd": 4200
},
"competing_liquidators": 2,
"recommended_gas": {
"priority_fee": "10000000000",
"max_fee": "100000000000"
}
},
"timestamp": "2026-01-15T10:30:00.123Z"
}

Market Update

Periodic market condition updates:

{
"type": "market_update",
"data": {
"protocol": "aave_v3",
"chain": "ethereum",
"positions_at_risk": 45,
"total_liquidatable_usd": 2500000,
"avg_liquidation_bonus": 0.05,
"gas_price_gwei": 25,
"top_opportunities": [
{
"user": "0xabc...",
"profit_usd": 5000,
"health_factor": 0.95
}
]
},
"timestamp": "2026-01-15T10:30:00Z"
}

Health Status

StatusHealth FactorDescription
HEALTHY>= 1.5Safe position
WARNING1.2 - 1.5Monitor closely
AT_RISK1.0 - 1.2Approaching liquidation
LIQUIDATABLE< 1.0Can be liquidated now

Subscription Filters

Send filter configuration after connecting:

{
"type": "configure",
"filters": {
"protocols": ["aave_v3", "compound_v3"],
"chains": ["ethereum", "arbitrum"],
"min_profit_usd": 100,
"min_collateral_usd": 10000,
"health_status": ["AT_RISK", "LIQUIDATABLE"],
"collateral_tokens": ["WETH", "WBTC", "stETH"],
"debt_tokens": ["USDC", "USDT", "DAI"]
}
}

Filter Options

FilterTypeDescription
protocolsstring[]Protocols to monitor
chainsstring[]Chains to monitor
min_profit_usdfloatMinimum estimated profit
min_collateral_usdfloatMinimum collateral value
health_statusstring[]Health statuses to include
collateral_tokensstring[]Filter by collateral type
debt_tokensstring[]Filter by debt type

Code Examples

Python - Liquidation Bot

import asyncio
import json
from dataclasses import dataclass
from typing import Optional
import websockets
from web3 import Web3

@dataclass
class LiquidationOpportunity:
user: str
protocol: str
chain: str
health_factor: float
debt_token: str
debt_to_cover: str
collateral_token: str
collateral_to_receive: str
estimated_profit_usd: float
gas_estimate: int
net_profit_usd: float

class LiquidationBot:
def __init__(
self,
jwt_token: str,
private_key: str,
rpc_url: str,
min_profit_usd: float = 50
):
self.jwt_token = jwt_token
self.min_profit_usd = min_profit_usd
self.w3 = Web3(Web3.HTTPProvider(rpc_url))
self.account = self.w3.eth.account.from_key(private_key)
self._ws = None

# Protocol contracts (simplified)
self.contracts = {
"aave_v3": {
"pool": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"
},
"compound_v3": {
"comet": "0xc3d688B66703497DAA19211EEdff47f25384cdc3"
}
}

async def connect(self):
"""Connect to liquidation stream."""
url = (
f"wss://api.axol.io/api/v1/defi/liquidations/stream"
f"?token={self.jwt_token}"
f"&chain=ethereum"
)
self._ws = await websockets.connect(url)
print("Connected to liquidation stream")

# Configure filters
await self._ws.send(json.dumps({
"type": "configure",
"filters": {
"protocols": ["aave_v3", "compound_v3"],
"min_profit_usd": self.min_profit_usd,
"health_status": ["AT_RISK", "LIQUIDATABLE"]
}
}))

async def monitor(self):
"""Monitor liquidation opportunities."""
while True:
try:
message = await self._ws.recv()
data = json.loads(message)

if data["type"] == "liquidation_alert":
opportunity = self._parse_opportunity(data["data"])
if opportunity and opportunity.net_profit_usd >= self.min_profit_usd:
await self._execute_liquidation(opportunity)

elif data["type"] == "position_update":
# Track positions approaching liquidation
health = data["data"]["health_factor"]
if health < 1.05:
print(f"Position at risk: {data['data']['user'][:10]}... HF: {health:.3f}")

except websockets.ConnectionClosed:
print("Connection closed, reconnecting...")
await asyncio.sleep(1)
await self.connect()

def _parse_opportunity(self, data: dict) -> Optional[LiquidationOpportunity]:
"""Parse liquidation opportunity from message."""
liq_data = data.get("liquidation_opportunity")
if not liq_data:
return None

return LiquidationOpportunity(
user=data["user"],
protocol=data["protocol"],
chain=data["chain"],
health_factor=data["health_factor"],
debt_token=liq_data["debt_token"],
debt_to_cover=liq_data["max_debt_to_cover"],
collateral_token=liq_data["collateral_token"],
collateral_to_receive=liq_data["collateral_to_receive"],
estimated_profit_usd=liq_data["estimated_profit_usd"],
gas_estimate=liq_data.get("gas_estimate", 350000),
net_profit_usd=liq_data.get("net_profit_usd", liq_data["estimated_profit_usd"])
)

async def _execute_liquidation(self, opp: LiquidationOpportunity):
"""Execute liquidation on-chain."""
print(f"\n{'='*50}")
print(f"EXECUTING LIQUIDATION")
print(f"User: {opp.user}")
print(f"Protocol: {opp.protocol}")
print(f"Health Factor: {opp.health_factor:.4f}")
print(f"Debt to cover: {opp.debt_to_cover} {opp.debt_token}")
print(f"Collateral: {opp.collateral_to_receive} {opp.collateral_token}")
print(f"Net Profit: ${opp.net_profit_usd:.2f}")
print(f"{'='*50}")

# Here you would:
# 1. Get flash loan for debt_to_cover amount
# 2. Call liquidation function on the protocol
# 3. Swap received collateral back to repay flash loan
# 4. Keep profit

# Simplified example for Aave V3:
if opp.protocol == "aave_v3":
# Build liquidation transaction
# pool = self.w3.eth.contract(
# address=self.contracts["aave_v3"]["pool"],
# abi=AAVE_POOL_ABI
# )
# tx = pool.functions.liquidationCall(
# collateral_asset,
# debt_asset,
# user,
# debt_to_cover,
# receive_a_token=False
# ).build_transaction({...})
pass

async def close(self):
if self._ws:
await self._ws.close()


async def main():
bot = LiquidationBot(
jwt_token="YOUR_JWT_TOKEN",
private_key="YOUR_PRIVATE_KEY",
rpc_url="https://eth.llamarpc.com",
min_profit_usd=100
)

await bot.connect()
await bot.monitor()

asyncio.run(main())

TypeScript - Monitoring Dashboard

interface Position {
user: string;
protocol: string;
chain: string;
health_factor: number;
health_status: 'HEALTHY' | 'WARNING' | 'AT_RISK' | 'LIQUIDATABLE';
collateral_usd: number;
debt_usd: number;
profit_usd?: number;
}

interface LiquidationAlert {
type: 'liquidation_alert';
priority: 'high' | 'medium' | 'low';
data: {
user: string;
protocol: string;
chain: string;
health_factor: number;
liquidation_opportunity: {
estimated_profit_usd: number;
net_profit_usd: number;
};
};
}

interface MarketUpdate {
type: 'market_update';
data: {
protocol: string;
positions_at_risk: number;
total_liquidatable_usd: number;
};
}

class LiquidationDashboard {
private ws: WebSocket | null = null;
private positions: Map<string, Position> = new Map();
private alerts: LiquidationAlert[] = [];

constructor(private jwtToken: string) {}

async connect(): Promise<void> {
const url = `wss://api.axol.io/api/v1/defi/liquidations/stream?token=${this.jwtToken}`;

return new Promise((resolve, reject) => {
this.ws = new WebSocket(url);

this.ws.onopen = () => {
console.log('Connected to liquidation stream');
this.configure();
resolve();
};

this.ws.onmessage = (event) => {
this.handleMessage(JSON.parse(event.data));
};

this.ws.onerror = (error) => reject(error);
});
}

private configure(): void {
this.ws?.send(JSON.stringify({
type: 'configure',
filters: {
protocols: ['aave_v3', 'compound_v3', 'morpho'],
chains: ['ethereum', 'arbitrum'],
health_status: ['WARNING', 'AT_RISK', 'LIQUIDATABLE'],
min_collateral_usd: 1000
}
}));
}

private handleMessage(message: any): void {
switch (message.type) {
case 'position_update':
this.updatePosition(message.data);
break;
case 'liquidation_alert':
this.handleAlert(message as LiquidationAlert);
break;
case 'market_update':
this.updateMarketStats(message as MarketUpdate);
break;
}
}

private updatePosition(data: any): void {
const key = `${data.protocol}:${data.user}`;
const position: Position = {
user: data.user,
protocol: data.protocol,
chain: data.chain,
health_factor: data.health_factor,
health_status: data.health_status,
collateral_usd: data.collateral.total_usd,
debt_usd: data.debt.total_usd,
profit_usd: data.liquidation_opportunity?.estimated_profit_usd
};

this.positions.set(key, position);
this.renderDashboard();
}

private handleAlert(alert: LiquidationAlert): void {
this.alerts.unshift(alert);
if (this.alerts.length > 100) this.alerts.pop();

// Play sound for high priority alerts
if (alert.priority === 'high') {
console.log('\n*** HIGH PRIORITY LIQUIDATION ***');
console.log(`User: ${alert.data.user.slice(0, 10)}...`);
console.log(`Profit: $${alert.data.liquidation_opportunity.net_profit_usd.toFixed(2)}`);
}

this.renderDashboard();
}

private updateMarketStats(update: MarketUpdate): void {
console.log(`\n[Market] ${update.data.protocol}: ${update.data.positions_at_risk} at risk, $${(update.data.total_liquidatable_usd / 1e6).toFixed(2)}M liquidatable`);
}

private renderDashboard(): void {
console.clear();
console.log('=== LIQUIDATION DASHBOARD ===\n');

// Sort by health factor
const sorted = Array.from(this.positions.values())
.filter(p => p.health_status !== 'HEALTHY')
.sort((a, b) => a.health_factor - b.health_factor);

console.log('AT-RISK POSITIONS:\n');
console.log('User | Protocol | HF | Status | Collateral | Profit');
console.log('-'.repeat(75));

sorted.slice(0, 20).forEach(p => {
const status = this.formatStatus(p.health_status);
console.log(
`${p.user.slice(0, 8)}... | ${p.protocol.padEnd(9)} | ${p.health_factor.toFixed(4)} | ${status} | $${this.formatUsd(p.collateral_usd)} | $${this.formatUsd(p.profit_usd || 0)}`
);
});

console.log(`\nTotal positions: ${this.positions.size}`);
console.log(`Recent alerts: ${this.alerts.length}`);
}

private formatStatus(status: string): string {
const colors: Record<string, string> = {
LIQUIDATABLE: '\x1b[31mLIQUIDAT\x1b[0m',
AT_RISK: '\x1b[33mAT_RISK \x1b[0m',
WARNING: '\x1b[36mWARNING \x1b[0m',
HEALTHY: '\x1b[32mHEALTHY \x1b[0m'
};
return colors[status] || status;
}

private formatUsd(value: number): string {
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
return value.toFixed(0);
}

getTopOpportunities(limit = 10): Position[] {
return Array.from(this.positions.values())
.filter(p => p.health_status === 'LIQUIDATABLE' && p.profit_usd)
.sort((a, b) => (b.profit_usd || 0) - (a.profit_usd || 0))
.slice(0, limit);
}

close(): void {
this.ws?.close();
}
}

// Usage
async function main() {
const dashboard = new LiquidationDashboard(process.env.JWT_TOKEN!);
await dashboard.connect();

// Periodically show top opportunities
setInterval(() => {
const top = dashboard.getTopOpportunities(5);
if (top.length > 0) {
console.log('\n=== TOP OPPORTUNITIES ===');
top.forEach((p, i) => {
console.log(`${i + 1}. ${p.protocol}: $${p.profit_usd?.toFixed(2)} profit (HF: ${p.health_factor.toFixed(4)})`);
});
}
}, 30000);
}

main().catch(console.error);

Best Practices

1. Use Health Threshold Filters

Don't stream all positions - focus on those near liquidation:

{
"filters": {
"health_status": ["AT_RISK", "LIQUIDATABLE"],
"min_profit_usd": 50
}
}

2. Account for Gas Costs

Always check net profit after gas:

net_profit = opportunity.estimated_profit_usd - (gas_price * gas_estimate * eth_price)
if net_profit < min_profit:
return # Skip unprofitable liquidations

3. Implement Competing Detection

Watch for other liquidators:

if data.get("competing_liquidators", 0) > 3:
# High competition - increase gas or skip
pass

4. Use Flash Loans

For capital-efficient liquidations:

# Don't need to hold debt tokens upfront
# 1. Flash loan debt amount
# 2. Execute liquidation
# 3. Swap collateral to repay flash loan
# 4. Keep profit

See Also