TL;DR: Dieser Artikel zerlegt die Mechanik von Crypto Lending und Liquidationen in Web3-Protokollen (Aave v3, Morpho Blue). Wir beleuchten die Kernmetriken (Health Factor, LTV, Liquidation Threshold), Slippage-Szenarien bei Chainlink-Oracle-Glitches und schauen uns ein Python-Monitoring-Script für automatisierte Liquidationen an.
Wie Crypto Lending funktioniert und wo die Liquidations-Fallen lauern
Ein Krypto-Kredit ist kein klassischer Banktermin, bei dem der Berater im Feinstrickpulli deine Gehaltsabrechnung prüft und die Hypothek auf Omas Reihenhaus absegnet. Im Web3 interessiert sich keine Sau für deinen Namen, deine Schufa oder deine Lebensplanung. Hier zählt einzig die kalte Mathematik von Smart Contracts: Besicherung (Collateral) hinterlegen, überbesichertes Darlehen (overcollateralized loan) ziehen, Health Factor aus den Augen verlieren – und zack, zerlegen die Liquidations-Bots deine Position in Bruchteilen einer Millisekunde.
Nehmen wir das Setup auf Aave v3 oder Morpho Blue. Du droppst $10.000 in WBTC im Protocol, um $6.000 in USDC rauszuziehen. Warum das Ganze? Damit du deine Bitcoins nicht verkaufen und keine Kapitalertragsteuer abdrücken musst, während du gleichzeitig Cash hast, um den nächsten Shitcoin "to the moon" zu hebeln oder dein Hardware-Upgrade zu finanzieren. Klingt nach dem Heiligen Gral. Auf dem Papier.
Und dann kommt der klassische Market-Dump: 18% nach unten innerhalb von 15 Minuten. Die Liquidität im Orderbuch verpufft. Das Chainlink-Oracle hinkt wegen Netzwerkauslastung ein paar Blöcke hinterher, und deine Position rauscht direkt in die Liquidations-Hölle.
Anatomie einer Liquidation: Warum dein Collateral instant weg ist
Die eine Metrik, die du 24/7 auf dem Schirm haben musst, ist der Health Factor (HF). Die Formel ist denkbar simpel:
HF = ∑ (Collaterali × LTVi) / Total Borrowed
Solange HF > 1 ist, bist du safe. Fällt der HF ≤ 1, wird deine Position Freiwild für jeden im Netzwerk, der ein passendes Bot-Script am Laufen hat.
| Parameter | Erklärung | Praxisbeispiel (ETH Pool) |
|---|---|---|
| Max LTV | Maximaler Beleihungsauslauf relativ zum Collateral | 80% ($800 Kredit auf $1.000 ETH Collateral) |
| Liquidation Threshold (LT) | Schwelle, ab der die Liquidation getriggert werden kann | 82.5% |
| Liquidation Bonus | Der Rabatt aufs Collateral, den der Liquidator einsackt | 5% |
| Reserve Factor | Protokoll-Cut der Zinserträge | 15% |
Aber Verlass dich nicht drauf. Auf dem Papier klingen 82,5% nach Puffer, aber in der Realität schlagen Slippage-Phantome und Mainnet-Gas-Preise gnadenlos zu. Sobald ein Push-Oracle den neuen Preis in den Contract drückt, stehen die MEV-Bots in der Flashbots-Bundle-Queue schon bereit. Sie kaufen deine Position mit 5% Discount auf, tilgen die Schuld sofort via Flash Loan auf Uniswap v3 und streichen die Marge ein.
Das Ergebnis? Du sitzt auf den USDC, die du wahrscheinlich schon irgendwo verbrannt hast, aber deine WBTC sind weg. Das Protokoll kassiert die Penalty, der Bot snaipt den Spread. Du bist reingeschifft.
Liquidator-Script: Wie Bots deine Schwäche eiskalt ausnutzen
Ich hab selbst vor ein paar Monaten auf Arbitrum gesehen, wie ein verbuggtes Oracle innerhalb von drei Blöcken $200k an Deposits ins Nirwana geschickt hat. Ein einfaches Liquidations-Script ist schnell zusammengeschustert – der wahre Unterschied liegt in den Millisekunden und dem direkten Draht zum MEV-Relay.
Hier ist ein funktionaler Python-Prototyp via web3.py, der Kreditnehmer im LendingPool überwacht und sofort eine Transaktion baut, sobald der HF unter 1,0 droppt.
import os
import time
from web3 import Web3
from web3.exceptions import ContractLogicError, TransactionNotFound
# ------------------------------------------------------------------------------
# 1. Konfiguration & Setup
# ------------------------------------------------------------------------------
RPC_URL = os.getenv("RPC_URL", "https://arb-mainnet.g.alchemy.com/v2/YOUR_API_KEY")
PRIVATE_KEY = os.getenv("PRIVATE_KEY", "0x_YOUR_PRIVATE_KEY")
w3 = Web3(Web3.HTTPProvider(RPC_URL))
if not w3.is_connected():
raise RuntimeError("RPC-Verbindung fehlgeschlagen. Überprüf deine RPC_URL.")
ACCOUNT = w3.eth.account.from_key(PRIVATE_KEY)
# Aave v3 Contracts auf Arbitrum
POOL_ADDRESS = Web3.to_checksum_address("0x794a61358D6845594F94dc1DB02A252b5b4814aD")
DATA_PROVIDER_ADDRESS = Web3.to_checksum_address("0x69FA0fee221AD11012B2f52097e8F6B7EAcF57E7")
TARGET_USER = Web3.to_checksum_address("0x0000000000000000000000000000000000000000")
COLLATERAL_ASSET = Web3.to_checksum_address("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f") # WBTC
DEBT_ASSET = Web3.to_checksum_address("0xaf88d065e77c8cC2239327C5EDb3A432268e5831") # USDC
# ------------------------------------------------------------------------------
# 2. ABIs
# ------------------------------------------------------------------------------
POOL_ABI = [
{
"inputs": [{"internalType": "address", "name": "user", "type": "address"}],
"name": "getUserAccountData",
"outputs": [
{"internalType": "uint256", "name": "totalCollateralBase", "type": "uint256"},
{"internalType": "uint256", "name": "totalDebtBase", "type": "uint256"},
{"internalType": "uint256", "name": "availableBorrowsBase", "type": "uint256"},
{"internalType": "uint256", "name": "currentLiquidationThreshold", "type": "uint256"},
{"internalType": "uint256", "name": "ltv", "type": "uint256"},
{"internalType": "uint256", "name": "healthFactor", "type": "uint256"}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "collateralAsset", "type": "address"},
{"internalType": "address", "name": "debtAsset", "type": "address"},
{"internalType": "address", "name": "user", "type": "address"},
{"internalType": "uint256", "name": "debtToCover", "type": "uint256"},
{"internalType": "bool", "name": "receiveAToken", "type": "bool"}
],
"name": "liquidationCall",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
DATA_PROVIDER_ABI = [
{
"inputs": [
{"internalType": "address", "name": "asset", "type": "address"},
{"internalType": "address", "name": "user", "type": "address"}
],
"name": "getUserReserveData",
"outputs": [
{"internalType": "uint256", "name": "currentATokenBalance", "type": "uint256"},
{"internalType": "uint256", "name": "currentStableDebt", "type": "uint256"},
{"internalType": "uint256", "name": "currentVariableDebt", "type": "uint256"},
{"internalType": "uint256", "name": "principalStableDebt", "type": "uint256"},
{"internalType": "uint256", "name": "scaledVariableDebt", "type": "uint256"},
{"internalType": "uint256", "name": "stableBorrowRate", "type": "uint256"},
{"internalType": "uint256", "name": "liquidityRate", "type": "uint256"},
{"internalType": "uint40", "name": "stableRateLastUpdated", "type": "uint40"},
{"internalType": "bool", "name": "usageAsCollateralEnabled", "type": "bool"}
],
"stateMutability": "view",
"type": "function"
}
]
ERC20_ABI = [
{
"inputs": [
{"internalType": "address", "name": "owner", "type": "address"},
{"internalType": "address", "name": "spender", "type": "address"}
],
"name": "allowance",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{"internalType": "address", "name": "spender", "type": "address"},
{"internalType": "uint256", "name": "amount", "type": "uint256"}
],
"name": "approve",
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}], # FIX: Typ auf bool korrigiert
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{"internalType": "address", "name": "account", "type": "address"}],
"name": "balanceOf",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
}
]
pool = w3.eth.contract(address=POOL_ADDRESS, abi=POOL_ABI)
data_provider = w3.eth.contract(address=DATA_PROVIDER_ADDRESS, abi=DATA_PROVIDER_ABI)
debt_token = w3.eth.contract(address=DEBT_ASSET, abi=ERC20_ABI)
tx_in_flight = False
# ------------------------------------------------------------------------------
# 3. Safe Gas & Nonce Helpers
# ------------------------------------------------------------------------------
def get_fee_parameters():
"""Fragt EIP-1559 Parameter ab mit Fallback für Netzwerke ohne baseFeePerGas."""
latest_block = w3.eth.get_block('latest')
base_fee = latest_block.get('baseFeePerGas')
if base_fee is not None:
max_priority_fee = w3.to_wei(0.1, 'gwei')
max_fee = base_fee * 2 + max_priority_fee
return {
'maxFeePerGas': max_fee,
'maxPriorityFeePerGas': max_priority_fee
}
else:
# Fallback für Legacy-Netzwerke / Custom L2s
return {'gasPrice': w3.eth.gas_price}
def ensure_allowance(required_amount: int) -> bool:
"""Prüft und setzt das Approve inklusive zwingender Check der Receipt-Status."""
current_allowance = debt_token.functions.allowance(ACCOUNT.address, POOL_ADDRESS).call()
if current_allowance >= required_amount:
return True
print("[*] Zu wenig Allowance. Feure Approve-TX...")
nonce = w3.eth.get_transaction_count(ACCOUNT.address, 'pending')
tx_params = {
'chainId': w3.eth.chain_id,
'from': ACCOUNT.address,
'nonce': nonce,
**get_fee_parameters()
}
tx = debt_token.functions.approve(POOL_ADDRESS, 2**256 - 1).build_transaction(tx_params)
signed_tx = w3.eth.account.sign_transaction(tx, PRIVATE_KEY)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=30)
if receipt['status'] == 1:
print(f"[+] Approve erfolgreich confirmed in Block {receipt['blockNumber']}")
return True
else:
print(f"[!] FEHLER: Approve-Transaktion ist gefailt (reverted)!")
return False
# ------------------------------------------------------------------------------
# 4. Core Liquidation Logic
# ------------------------------------------------------------------------------
def execute_liquidation(user_address: str):
global tx_in_flight
if tx_in_flight:
print("[!] Lokaler Prozess blockiert (Transaktion fliegt bereits).")
return
# FIX #9: Schneller Re-Check des HF vor dem teuren Setup
account_data = pool.functions.getUserAccountData(user_address).call()
health_factor = account_data[5] / 10**18
if health_factor >= 1.0:
print(f"[-] Abgebrochen: Position-HF liegt jetzt bei {health_factor:.6f} (>= 1.0)")
return
# FIX #11: Exakten Schuldenstand in USDC-Token berechnen
reserve_data = data_provider.functions.getUserReserveData(DEBT_ASSET, user_address).call()
actual_debt_tokens = reserve_data[1] + reserve_data[2] # stable + variable
if actual_debt_tokens == 0:
print("[!] Keine Schulden für das gewählte Asset vorhanden.")
return
debt_to_cover = actual_debt_tokens // 2
# Eigene USDC-Balance prüfen
our_balance = debt_token.functions.balanceOf(ACCOUNT.address).call()
if our_balance < debt_to_cover:
print(f"[!] Nicht genug USDC in der Wallet. Benötigt: {debt_to_cover}, Vorhanden: {our_balance}")
debt_to_cover = our_balance
if debt_to_cover == 0:
return
# FIX #8: Checken, ob das Approve sauber durchging
if not ensure_allowance(debt_to_cover):
return
# Nutzen die 'pending' Nonce gegen Account-interne TX-Conflicts
nonce = w3.eth.get_transaction_count(ACCOUNT.address, 'pending')
tx_params = {
'chainId': w3.eth.chain_id,
'from': ACCOUNT.address,
'nonce': nonce,
**get_fee_parameters()
}
# FIX #5: Simulation direkt vor dem Builden
try:
estimated_gas = pool.functions.liquidationCall(
COLLATERAL_ASSET,
DEBT_ASSET,
user_address,
debt_to_cover,
False
).estimate_gas(tx_params)
tx_params['gas'] = int(estimated_gas * 1.2)
except ContractLogicError as e:
print(f"[!] Smart Contract hat die liquidationCall-Simulation gekillt: {e}")
return
except Exception as e:
print(f"[!] Fehler bei der Gas-Estimation: {e}")
return
# Build & Send
try:
tx_in_flight = True
tx_data = pool.functions.liquidationCall(
COLLATERAL_ASSET,
DEBT_ASSET,
user_address,
debt_to_cover,
False
).build_transaction(tx_params)
signed_tx = w3.eth.account.sign_transaction(tx_data, PRIVATE_KEY)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print(f"[+] Transaktion rausgejagt! Tx Hash: {tx_hash.hex()}")
# FIX #10: Receipt-Status der Liquidation auswerten
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=30)
if receipt['status'] == 1:
print(f"[ERFOLG] Position erfolgreich in Block {receipt['blockNumber']} liquidiert!")
else:
print(f"[FAIL] Transaktion zwar gemined, aber REVERT (Status 0). Gas ist verpufft.")
except Exception as e:
print(f"[!] Kritischer Fehler beim Senden: {e}")
finally:
tx_in_flight = False
# ------------------------------------------------------------------------------
# 5. Monitoring
# ------------------------------------------------------------------------------
def monitor_user(user_address: str):
account_data = pool.functions.getUserAccountData(user_address).call()
health_factor = account_data[5] / 10**18
print(f"Target: {user_address} | Health Factor: {health_factor:.6f}")
if health_factor < 1.0:
print("[!] LIQUIDATIONS-TARGET GEFUNDEN!")
execute_liquidation(user_address)
if __name__ == "__main__":
while True:
try:
monitor_user(TARGET_USER)
time.sleep(1)
except KeyboardInterrupt:
print("\n Bot manuell gestoppt.")
break
except Exception as e:
print(f"ERROR: {e}")
time.sleep(3)Auf L2 gibt es keinen Mempool. Gar keinen. Da gilt strikt FCFS (First Come, First Served). Wer zuerst kommt, mahlt zuerst.
Während dein Script also gemütlich vor sich hin time.sleep(1) ausführt, hängen spezialisierte HFT-Bots per Socket direkt in demselben Rack, in dem der Validator läuft. Deine Chancen, einen Margin Call manuell zu frontrunnen? Absolut NULL.
Wo die Yield wirklich herkommt (und warum trotzdem alle hebeln)
Warum geht man das Risiko überhaupt ein?
- Zinsarbitrage (Interest Rate Arbitrage). Du leihst dir USDT für 4% APY auf Plattform A und wirfst sie in einen Vault mit 12% APY auf Plattform B. Die 8% Delta nimmst du mit. Aber Vorsicht vor dem Smart-Contract-Risiko: Wenn Plattform B depeggt oder gehackt wird (z. B. Reentrancy-Attacke), schuldest du Plattform A immer noch echtes Geld.
- Liquidity Delegation & Collateral Tokenization. Schau dir Pendle oder Morpho an. Du hinterlegst stETH, ziehst Stables und kaufst damit PTs (Principal Tokens) mit fixer Rendite. Komplex? Definitiv. Profitabel? Absolut – bis zum ersten Oracle-Glitches bei den LST-Token.
- Steuerliche Optimierung. Realisierte Gewinne = Steuerpflicht. Ein Kredit gegen dein stetig steigendes Asset = kein Veräußerungsgeschäft. Wo kein Verkauf, da keine Steuer. Pragmatisch? Vollkommen.
Was die meisten dabei vergessen, sind kaskadierende Liquidationen. Im Mai 2021 oder beim FTX-Kollaps sind komplette Protokolle illiquide geworden (Bad Debt), weil die Preise des Collaterals schneller gecrasht sind, als die Bots die Schulden abwickeln konnten. Am Ende hat's die normalen Lender erwischt, die einfach nur ihre Stables für ein paar Prozent Zinsen zur Verfügung gestellt haben.
Isolierte Margin-Pools vs. Cross-Margin: Wo die Landmine vergraben liegt
Viele Neulinge werden eiskalt erwischt, weil sie schlichtweg keine Ahnung von der Architektur der Risikobewertung haben. Es gibt zwei grundlegend verschiedene Ansätze: Cross-Margin (der große Sammeltopf) und Isolated Markets (isolierte Märkte wie Morpho oder Euler v2).
Bei Cross-Margin (der Standard bei Aave) fungiert dein gesamtes Kollateral-Portfolio als einziger Schutzschild. Du zahlst WBTC, ETH und DAI ein — und leihst dir USDC. Wenn nur der WBTC einbricht, fängt dich das Puffer-Polster aus DAI ab. Praktisch? Definitive. Aber wenn eines deiner hinterlegten Assets komplett in den Abgrund rauscht (man denke an den stETH-Depeg 2022 oder den Scam eines beliebigen Gebrückten Tokens), reißt es deine GESAMTE Position mit runter. Du wirst bis auf das letzte Hemd liquidiert.
In Isolierten Pools begrenzt du das Risiko auf ein ganz bestimmtes Paar. Zum Beispiel ein maßgeschneiderter wstETH / USDC Vault.
[Dein Deposit] ---> [Isolierter Pool A (ETH/USDC)] ---> Risiko auf Pool A beschränkt
---> [Isolierter Pool B (PEPE/USDC)] ---> PEPE-Scam = Verlust nur in Pool BRauscht ein beliebiger Shitcoin in Pool B um 99% nach unten, verlierst du genau das, was du in Pool B gesteckt hast. Dein wertvolles Ether in Pool A sitzt völlig sicher im Trockenen.
Klar, drei verschiedene Positionen zu verwalten ist ein absoluter Krampf und frisst ordentlich Gas-Gebühren. Bei hoher Volatilität ist es aber die einzig vernünftige Überlebensstrategie.
Orakel: Wie ihr komplett rekt geht, ohne dass der Smart Contract gehackt wird
Der Smart Contract eines Lending-Protokolls ist von Natur aus taub und blind. Er hat keinen blassen Schimmer, was ETH in dieser Sekunde wert ist. Er vertraut blind dem Orakel. Und genau hier beginnt der eigentliche Albtraum.
Das sind die fiesesten Schwachstellen von Orakeln, an denen regelmäßig Deposits verbrennen:
- Stale Prices (Abgestandene Preise). Das Orakel pusht den Preis nicht jede Sekunde (das würde jeden Provider an den Gas-Gebühren ruinieren), sondern erst bei Abweichungen von sagen wir 0,5% oder im 24-Stunden-Takt (Heartbeat). Wenn der Markt innerhalb von 3 Sekunden einen aggressiven Wick von -5% nach unten zieht und sofort wieder hochgekauft wird, kann das Orakel den Preis PUNKTGENAU AM TIEFSTPUNKT aktualisieren. Der Contract liest einen „0.99 HF“, gibt den Liquidatoren grünes Licht und löscht dich aus. Der Markt erholt sich im nächsten Moment, aber du stehst mit leeren Händen da.
- Illiquid DEX Feeds (Spot-Preis-Manipulation). Nutzt das Protokoll ein TWAP-Orakel auf Uniswap v3 für ein illiquides Token, kann ein Wal mit einer einzigen riesigen Verkaufsorder den Preis in den Boden stampfen, eine Liquidationskaskade im Lending auslösen und das Asset danach spottbillig zurückkaufen. Ein absoluter Klassiker.
Die folgende Tabelle zeigt die gnadenlose Mathematik der Liquidationsverluste bei verschiedenen Einbrüchen der Sicherheit (bei einem anfänglichen LTV von 75% und einem Liquidationsbonus von 5%):
| Preiseinbruch der Sicherheit | Health Factor | Positions-Status | Verlust des Kreditnehmers vom Eindeckungskapital |
|---|---|---|---|
| -5% | 1.26 | Grüner Bereich | 0% (nur unrealisierter Buchverlust auf die Sicherheit) |
| -15% | 1.13 | Risikozone | 0% (Sicherheiten nachschießen erforderlich) |
| -25% | 0.99 | LIQUIDATION | ~15-20% (Liquidationsstrafe + Spread) |
| -40% (Flash Crash) | < 0.80 | Totalverlust | 100% der Sicherheit weg (dir bleibt nur der Kredit) |
Überlebens-Checkliste: Kredite aufnehmen, ohne vor die Hunde zu gehen
Wenn du dich schon in dezentrale Kredite wagst, halte dich an kompromisslose Risiko-Hygiene. Ich habe mir diese Regeln nach ein paar schmerzhaften Reverts und Zwangsliquidierungen selbst auf die harte Tour eingebrannt.
- Halte deinen HF strikt zwischen mindestens 1.5 und 1.8. Vergiss gefährliche Spielchen mit einem HF von 1.05. Eine einzige rote Kerze auf dem Chart und du bist Geschichte.
- Automatisiere das Nachschießen (Self-Kicker). Nutze Services wie Gelato Automation oder Chainlink Automation. Schreibe einen simplen Bot, der sofort Stablecoins aus deiner Wallet in den Contract schiebt, um Schulden zu tilgen, sobald der HF unter 1.2 fällt.
- Behalte die Orakel im Auge. Wisse genau, welches Orakel in deinem Pool läuft. Bei Chainlink Push-Type: überprüfe den Heartbeat. Bei Pyth: checke die Confidence Intervals.
- Hedge deine Position über Perps. Du hast einen Kredit auf deine ETH aufgenommen? Öffne einen Short auf ETH bei einem Derivate-DEX mit 1x Hebel in Höhe der Kreditsumme. Das friert dein Risiko in US-Dollar ein (Delta-Neutral).
Crypto-Lending ist ein mächtiger Hebel für dein Kapital, aber in den Händen von Unberechenbaren wird es zur Guillotine mit Countdown. Dem Smart Contract sind deine Markterwartungen nämlich völlig scheißegal.