TL;DR: Neste artigo, vamos escovar os bits do funcionamento do crypto lending e das liquidações em protocolos Web3 (Aave v3, Morpho Blue). Vamos analisar as principais métricas (Health Factor, LTV, Liquidation Threshold), os cenários de slippage monstro quando os oráculos da Chainlink engasgam e, de quebra, ver um script em Python para monitorar e liquidar posições automaticamente.
Como funciona o crypto lending e onde moram os riscos de liquidação
Empréstimo em crypto não tem nada a ver com banco tradicional, onde o gerente olha seu holerite e aprova o financiamento dando o apartamento da sua avó como garantia. No Web3, todo mundo está cagando para o seu nome, seu score no Serasa ou seus planos de vida. Aqui só vale a matemática fria dos smart contracts: colocou colateral (collateral), pegou um empréstimo super-garantido (overcollateralized loan), moscou no Health Factor — os bots liquidadores vão engolir suas moedas com farinha em uma fração de milissegundo.
Pega o cenário clássico na Aave v3 ou Morpho Blue. Você deposita $10.000 em WBTC para pegar $6.000 em USDC. Pra quê? Pra não precisar vender seu Bitcoin, não realizar ganho de capital (fugindo do imposto) e ainda levantar uma grana viva para mandar "to the moon" em uma shitcoin qualquer ou dar um upgrade no seu setup. No papel, parece a oitava maravilha do mundo.
Só que aí o mercado derrete 18% em questão de 15 minutos. A liquidez do order book some no vento. O oráculo da Chainlink atualiza o preço com atraso de alguns blocos por causa do congestionamento da rede, e a sua posição vai direto para o ralo da liquidação.
Anatomia da liquidação: por que seu colateral evapora num piscar de olhos
A métrica sagrada que você precisa monitorar 24/7 é o Health Factor (HF). A fórmula é ridiculamente simples:
HF = ∑ (Collaterali × LTVi) / Total Borrowed
Se o HF > 1, você tá suave. Se o HF ≤ 1, sua posição vira festa americana: qualquer um na rede com um script rodando pode te liquidar.
| Parâmetro | Explicação | Exemplo real (Pool de ETH) |
|---|---|---|
| Max LTV | Percentual máximo que você pode pegar emprestado sobre o colateral | 80% ($800 de empréstimo para $1000 em ETH) |
| Liquidation Threshold (LT) | O limite em que a posição fica liberada para ser liquidada | 82.5% |
| Liquidation Bonus | O desconto no colateral que o liquidador embolsa | 5% |
| Reserve Factor | A fatia do protocolo em cima do rendimento dos juros | 15% |
Calma lá, não se iluda. Na teoria, 82.5% parece uma margem segura, mas na prática você precisa colocar na conta o slippage fantasma e o gas da mainnet. Quando o oráculo do tipo push envia o preço atualizado para o contrato, os bots de MEV já estão na fila esperando no bundle do Flashbots. Eles compram sua posição com 5% de desconto, quitam sua dívida na hora usando um flash loan na Uniswap v3 e tiram o lucro líquido limpinho.
O resultado? Você fica com os USDC que já torrou em algum lugar, mas o seu WBTC já era. O protocolo cobra a taxa, o bot pega o spread e você fica no prejuízo total.
Script do liquidador: como os bots farejam sua fragilidade
Há uns seis meses eu vi ao vivo na Arbitrum um oráculo bugado mandar $200k em depósitos pro espaço em três blocos. Escrever um script de liquidação é até simples, mas o que manda mesmo são os milissegundos e ter acesso direto a um relay de MEV.
Dá uma olhada nesse protótipo funcional em Python usando web3.py. Ele monitora o tomador no LendingPool e monta a transação na hora se o HF cair abaixo de 1.
import os
import time
from web3 import Web3
from web3.exceptions import ContractLogicError, TransactionNotFound
# ------------------------------------------------------------------------------
# 1. Configuração e Conexão
# ------------------------------------------------------------------------------
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("Falha na conexão RPC. Checa a sua RPC_URL.")
ACCOUNT = w3.eth.account.from_key(PRIVATE_KEY)
# Contratos da Aave v3 na 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. ABI
# ------------------------------------------------------------------------------
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: Tipo corrigido para bool
"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. Helpers Seguros para Gas e Nonce
# ------------------------------------------------------------------------------
def get_fee_parameters():
"""Busca os parâmetros EIP-1559 com fallback para redes sem 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 para redes Legacy / L2s customizadas
return {'gasPrice': w3.eth.gas_price}
def ensure_allowance(required_amount: int) -> bool:
"""Valida e manda o approve com verificação obrigatória do receipt status."""
current_allowance = debt_token.functions.allowance(ACCOUNT.address, POOL_ADDRESS).call()
if current_allowance >= required_amount:
return True
print("[*] Allowance insuficiente. Mandando approve...")
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 confirmado com sucesso no bloco {receipt['blockNumber']}")
return True
else:
print(f"[!] ERRO: Transação de approve deu revert!")
return False
# ------------------------------------------------------------------------------
# 4. Lógica Principal da Liquidação
# ------------------------------------------------------------------------------
def execute_liquidation(user_address: str):
global tx_in_flight
if tx_in_flight:
print("[!] Processo local bloqueado (já tem transação voando).")
return
# FIX #9: Re-check rápido do HF antes de preparar a TX cara
account_data = pool.functions.getUserAccountData(user_address).call()
health_factor = account_data[5] / 10**18
if health_factor >= 1.0:
print(f"[-] Abortado: HF da posição mudou para {health_factor:.6f} (>= 1.0)")
return
# FIX #11: Cálculo do débito exato em tokens USDC
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("[!] Essa conta não tem dívida no token selecionado.")
return
debt_to_cover = actual_debt_tokens // 2
# Validação do nosso próprio saldo de USDC
our_balance = debt_token.functions.balanceOf(ACCOUNT.address).call()
if our_balance < debt_to_cover:
print(f"[!] USDC insuficiente na carteira. Precisa: {debt_to_cover}, Tem: {our_balance}")
debt_to_cover = our_balance
if debt_to_cover == 0:
return
# FIX #8: Garantir que o approve passou redondinho
if not ensure_allowance(debt_to_cover):
return
# Usando nonce 'pending' para evitar treta de conflito na mesma conta
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: Simulação colada no momento de buildar a TX
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"[!] O smart contract rejeitou a simulação da liquidationCall: {e}")
return
except Exception as e:
print(f"[!] Erro ao estimar o gas: {e}")
return
# Montagem e envio
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"[+] Transação enviada! Tx Hash: {tx_hash.hex()}")
# FIX #10: Análise do receipt da liquidação
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=30)
if receipt['status'] == 1:
print(f"[SUCESSO] Posição liquidada com sucesso no bloco {receipt['blockNumber']}!")
else:
print(f"[FAU] Transação foi minerada, mas deu REVERT (Status 0). Gas jogado fora.")
except Exception as e:
print(f"[!] Erro crítico ao enviar: {e}")
finally:
tx_in_flight = False
# ------------------------------------------------------------------------------
# 5. Monitoramento
# ------------------------------------------------------------------------------
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("[!] POSIÇÃO PRONTA PRA LIQUIDAÇÃO DETECTADA!")
execute_liquidation(user_address)
if __name__ == "__main__":
while True:
try:
monitor_user(TARGET_USER)
time.sleep(1)
except KeyboardInterrupt:
print("\n Bot interrompido pelo usuário.")
break
except Exception as e:
print(f"ERROR: {e}")
time.sleep(3)Em L2 não existe mempool. Simplesmente não existe. Tudo roda no esquema FCFS (First Come, First Served). Quem chega primeiro leva.
Enquanto o seu script fica travado no time.sleep(1), os bots de HFT da gringa estão conectados via socket no mesmo rack onde roda o validador. A sua chance de passar na frente de um margin call no braço é ZERO.
Onde tá o verdadeiro ouro (e por que todo mundo continua se alavancando)
Por que correr tanto risco?
- Arbitragem de Taxas (Interest Rate Arbitrage). Você pega USDT a 4% APY na plataforma A e joga num Vault com 12% APY na plataforma B. O delta de 8% vai direto pro seu bolso. Mas ó: tem risco de smart contract na jogada. Se a plataforma B perder o peg ou sofrer um reentrancy hack, você continua devendo dinheiro de verdade para a plataforma A.
- Delegação de Liquidez e Tokenização de Colateral. Dá uma olhada no Pendle ou no Morpho. Você deposita stETH, pega stables e com elas compra PT (Principal Tokens) com rendimento fixo. É complexo? Pra caramba. Dá lucro? Muito — até estourarem os primeiros problemas com o oráculo dos LSTs.
- Otimização Fiscal. Realizar lucro = pagar imposto. Pegar empréstimo deixando um ativo valorizado como colateral = não teve venda. Sem venda, sem imposto. É pragmático? Demais.
O problema é que o pessoal esquece do efeito dominó das liquidações em cascata. Em maio de 2021 e no colapso da FTX, protocolos inteiros ficaram insolventes (bad debt) porque o preço do colateral despencou mais rápido do que a capacidade dos bots de liquidar as posições. No fim das contas, quem se deu mal foram os lenders comuns, que só queriam deixar suas stables rendendo um jurinho seguro.
Pools de Margem Isolada vs Cross-Margin: onde está a armadilha
Muito iniciante se dá mal por pura falta de conhecimento sobre a arquitetura de gestão de risco. Existem duas abordagens fundamentalmente diferentes: Cross-Margin (o famoso "saco sem fundo") e Isolated Markets (mercados isolados tipo Morpho ou Euler v2).
No Cross-Margin (o padrão no Aave), todo o seu portfólio de colateral funciona como um escudo único. Você deposita WBTC, ETH e DAI — e pega USDC emprestado. Se só o WBTC derreter, a gordura de DAI te salva. Prático? Pra caramba. Mas se um dos seus colaterais despencar para o zero (lembra do depeg do stETH em 2022 ou de algum golpe com bridged token?), ele puxa a sua posição INTEIRA para o buraco. Você é liquidado até o último centavo.
Nos pools isolados, você isola o risco de um par específico. Por exemplo, um vault customizado de wstETH / USDC.
[Seu depósito] ---> [Pool Isolado A (ETH/USDC)] ---> Risco limitado ao Pool A
---> [Pool Isolado B (PEPE/USDC)] ---> PEPE virou pó = só o Pool B rodaSe a shitcoin do Pool B despencar 99%, você perde apenas o que colocou no Pool B. O seu ETH principal no Pool A continua intacto e seguro.
Sim, gerenciar três posições diferentes é um pé no saco. Custa mais gás. Mas é a única estratégia sensata quando você opera com alta volatilidade.
Oráculos: como você toma um scam sem qualquer hack no smart contract
O smart contract de um protocolo de lending é cego e surdo por padrão. Ele não faz a menor ideia de quanto custa o ETH neste exato segundo; ele simplesmente confia no oráculo. E é exatamente aí que o bicho pega.
Estes são os principais problemas em oráculos que queimam depósitos por aí:
- Stale Prices (Preços desatualizados). O oráculo não injeta o preço no contrato a cada segundo (isso faliria qualquer provedor em taxas de gás), mas sim quando há um desvio de, digamos, 0.5% ou a cada 24 horas (heartbeat). Se o mercado der uma agulhada violenta de 5% para baixo em 3 segundos e voltar imediatamente, o oráculo pode atualizar o preço BEM NO FUNDO. O contrato lê "0.99 HF", dá sinal verde para os liquidadores e você é zerado. O mercado recupera logo em seguida, mas você ficou a ver navios.
- Illiquid DEX Feeds (Manipulação no mercado à vista). Se o protocolo usa um oráculo TWAP da Uniswap v3 para um token ilíquido, uma baleia pode derrubar o preço com uma única venda pesada, engatilhar um cascata de liquidações no lending e depois recomprar o ativo no fundo. O clássico dos clássicos.
A tabela abaixo mostra a matemática real das perdas em liquidações para diferentes níveis de queda do colateral (considerando LTV inicial de 75% e Bônus de Liquidação de 5%):
| Queda no preço do colateral | Health Factor | Status da posição | Prejuízo do tomador sobre o depósito inicial |
|---|---|---|---|
| -5% | 1.26 | Seguro | 0% (apenas perda não realizada no colateral) |
| -15% | 1.13 | Zona de risco | 0% (precisa aportar mais colateral) |
| -25% | 0.99 | LIQUIDAÇÃO | ~15-20% (penalidade do liquidador + spread) |
| -40% (Flash Crash) | < 0.80 | Rekt total | 100% do colateral perdido (sobra só o valor emprestado) |
Checklist de sobrevivência: como pegar empréstimo DeFi sem se estrepar
Se você vai entrar no jogo de empréstimos descentralizados, siga regras rígidas de higiene operacional. Eu aprendi isso na pele, sentindo a dor de vários reverts indigestos e liquidações forçadas.
- Mantenha o HF sempre acima de 1.5 - 1.8. Esqueça essa história de operar no limite com HF em 1.05. Qualquer ruído no gráfico e você já era.
- Automatize a recomposição de margem (Self-Kicker). Use ferramentas como Gelato Automation ou Chainlink Automation. Crie um bot simples que, se o HF cair abaixo de 1.2, transfere stablecoins da sua carteira para o contrato e quita parte da dívida.
- Monitore os oráculos. Saiba exatamente qual oráculo roda no seu pool. Se for Chainlink do tipo push, acompanhe o heartbeat. Se for Pyth, confira os intervalos de confiança (confidence intervals).
- Faça hedge usando perps. Pegou empréstimo deixando ETH de garantia? Abra um short de ETH em uma DEX de derivativos com alavancagem de 1x no valor exato do empréstimo. Isso trava seu risco em dólares (delta-neutral).
Empréstimo em crypto é uma alavanca brutal para o seu capital, mas na mão de quem não sabe o que faz, vira uma guilhotina programada. O smart contract não quer nem saber para onde você acha que o mercado vai.