Aave V3-liquidatietracker
Volg en analyseer liquidatiegebeurtenissen op het Aave V3-protocol in realtime. Dit project laat zien hoe krachtig serverloze blockchain-datastreaming is met behulp van Quicknode Streams.
Inleiding
Volg en analyseer liquidatiegebeurtenissen op het Aave V3-protocol in realtime. Dit project toont de kracht aan van serverloze blockchain-datastreaming met behulp van QuicknodeStreams.
| Overzicht van het dashboard | Overzicht van liquidaties |
|---|---|
![]() |
Kenmerken
- 📊 Real-time monitoring van liquidaties
- 💰 Gedetailleerde statistieken en analyses
- 📈 Historische liquidatietrends
- 🔄 Analyse van de verdeling van activa
- ⚡ Serverloze architectuur
Aave-poolcontract en LiquidationCall-gebeurtenis
Overzicht van het Aave V3-poolcontract
De Pool.sol contract (voorMainnet Ethereum Mainnet, 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2) in het Aave V3-protocol vormt de kern van het systeem voor het verstrekken en aangaan van leningen. Het maakt stortingen, het aangaan van leningen, terugbetalingen en liquidaties mogelijk.
Te volgen gebeurtenis: LiquidationCall
De gebeurtenis `LiquidationCall ` wordt geactiveerd wanneer er een liquidatie plaatsvindt in het Aave-protocol. Deze gebeurtenis biedt essentiële informatie over de liquidatietransactie, waaronder het in beslag genomen onderpand en de afgeloste schuld.
// Gebeurtenissignatuur
// 0xe413a321e8681d831f4dbccbca790d2952b56f977908e45be37335533e005286
LiquidationCall(
adres onderpand,
adres schuldactief,
adres gebruiker,
uint256 te-dekken-schuld,
uint256 bedrag aan geliquideerd onderpand,
adres liquidator,
bool ontvangAToken
)
Evenementparameters
| Parameter | Type | Beschrijving |
|---|---|---|
onderpand-actief | adres | Het adres van het goed dat als onderpand in beslag wordt genomen. |
schuldActief | adres | Het adres van het vermogensbestanddeel dat de curator terugbetaalt. |
gebruiker | adres | Het adres van de kredietnemer die in liquidatie is. |
te dekken schuld | uint256 | Het bedrag aan schulden dat tijdens de liquidatie wordt afgelost. |
bedrag van de geliquideerde zekerheid | uint256 | Het bedrag aan onderpand dat door de curator in beslag is genomen. |
curator | adres | Het adres van de curator die de liquidatie uitvoert. |
receiveAToken | bool | Als waar, ontvangt de curator aTokens in plaats van onderpand. |
We zullen de gedecodeerde logbestanden van de Liquidatieoproep evenement om liquidaties op hetMainnet bij te houden.
Hoe het werkt
Deze applicatie maakt gebruik van een volledig serverloze architectuur om liquidatiegebeurtenissen in Aave V3 bij te houden en te analyseren:
- Gegevensverzameling: Streams houdt de ruwe blockchain-gebeurtenisgegevens bij voor de liquidatiegebeurtenissen van Aave V3, zowel historisch als in realtime
- EVM-decodering: Streams decoderen de ruwe gebeurtenisgegevens met behulp van de EVM-decoder om de relevante informatie te extraheren
- Gegevensverwerking: Met behulp van
viemen webhooks: we luisteren tijdelijk naar binnenkomende gegevens en verwerken de gedecodeerde gebeurtenisgegevens door tokenprijzen en tokengegevens op te halen - Gegevensopslag: Verrijkte gebeurtenisgegevens worden automatisch opgeslagen in de PostgreSQL-database
- Frontend-weergave: de React-applicatie haalt de gegevens op en geeft deze weer met realtime updates
Technologiestack
-
Frontend
- Reageren
- TypeScript
- TanStack Query voor het ophalen van gegevens
- Tailwind CSS voor opmaak
- shadcn/ui voor UI-componenten
- Recharts voor datavisualisatie
-
Backend
Architectuur

De architectuur laat zien hoe het systeem gebruikmaakt van de infrastructuur Quicknode om blockchain-gegevens efficiënt te verwerken:
- Quicknode houdt de gebeurtenissen in het Aave V3-contract in de gaten
- Gebeurtenissen worden ter verwerking naar de webhook-server verzonden
- De gegevens worden aangevuld met token- en prijsinformatie
- De verwerkte gegevens worden opgeslagen in Supabase
- De frontend haalt realtime gegevens op en geeft deze weer
Gegevensverzameling en -verwerking
Quicknode Streams
Quicknode Streams realtime blockchain-gegevens zonder dat je een node hoeft te draaien:
- Houdt Aave V3-contracten in de gaten op liquidatiegebeurtenissen
- Filtert en toont alleen relevante gebeurtenissen
- Zorgt voor een betrouwbare gegevensoverdracht dankzij mechanismen voor herhalingspogingen
- Er is geen onderhoud aan de infrastructuur nodig
Webhook-server
We maken gebruik van een webhook-server om binnenkomende streams te ontvangen en te valideren:
Daarnaast verrijken we streams door de volgende gegevenspunten toe te voegen:
- Token-namen/symbolen (WETH, USDC, enz.)
- Prijzen in USD op het moment van liquidatie
- Opgemaakte bedragen (bigints omzetten naar decimale getallen)
Aan de slag
Vereisten
- Node.js 18+
- npm of yarn
- Gratis proefperiode Quicknode account
- Gratis Supabase -account
- Webhook-server (ngrok of iets dergelijks)
Installatie
- Kloon de repository:
git clone https://github.com/quiknode-labs/qn-guide-examples.git
cdethereum
- Afhankelijkheden installeren:
npm installeren
-
Maak een nieuw project en een nieuwe database aan in Supabase. Sla het wachtwoord van de database op voor later gebruik.
-
Maak een
.envbestand en voeg je Supabase URL en publiceerbare sleutel. Je kunt de URL en de sleutel ophalen via het Supabase-dashboard door op de Verbinden knop in het project.
VITE_SUPABASE_URL=uw_Supabase-URL
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=uw_Supabase-publicatiesleutel
- Stel je database in aan de hand van het meegeleverde schema:
CREATE TABLE liquidations (
id SERIAL PRIMARY KEY,
liquidator_address character varying(42) NOT NULL,
liquidated_wallet character varying(42) NOT NULL,
collateral_asset character varying(42) NOT NULL,
collateral_asset_name character varying(100),
collateral_asset_symbol character varying(20),
collateral_asset_price numeric(20,6),
collateral_seized_amount numeric(78,18),
debt_asset character varying(42) NOT NULL,
debt_asset_name character varying(100),
debt_asset_symbol character varying(20),
debt_asset_price numeric(20,6),
debt_repaid_amount numeric(78,18),
transaction_hash character varying(66) NOT NULL,
block_number bigint NOT NULL,
receive_a_token boolean NOT NULL,
timestamp timestamp without time zone NOT NULL
);
- Start de ontwikkelingsserver:
npm run dev
Een webhook-server instellen
- Start je webhook-server door het volgende uit te voeren:
npm run server. - Voer in een aparte terminal het volgende uit:
ngrok http UW_POORTNUMMER. Voor dit project is de standaardwaarde ingesteld op3005maar je mag dit gerust aanpassen in.env. - Zodra de ngrok-server is opgezet, gebruik je de gegenereerde ngrok-link (bijvoorbeeld,
https://abcd.ngrok-free.app) en toevoegen/webhookendpoint we luisteren naar inkomende gegevens van Quicknode Streams.
Quicknode Streams instellen
- Maak een Quicknode aan op quicknode.com
- Een nieuwe stream aanmaken:
- Selecteer de blockchain en het netwerk (bijv. Ethereum Mainnet)
- Selecteer de dataset als
Blokken met kassabonnen - Selecteer de begin- en eindbloknummers (voor realtimegegevens, houd
Einde streamalsHet houdt niet op) - Selecteer het gedeelte ‘De payload aanpassen’ en voeg de volgende codeStreams toe
- Selecteer een testblok (bijv.,
18983377) en klik opTest uitvoeren - Kies de bestemming als
Webhooken voeg je webhook-URL toe met/webhookendpoint . - Indien gewenst kun je het beveiligingstoken van je Stream toevoegen in
.envom gebruik te maken van geavanceerde beveiligingsfuncties.
De contractadressen, testblokken en andere configuraties kunnen variëren, afhankelijk van het specifieke project en het netwerk waarmee je werkt. In deze app maken we gebruik vanMainnet Ethereum Mainnet het Aave V3-protocol.
Je kunt de volledige code voor de Streams hieronder vinden of hier bekijken.
Code voor Streams bekijken
// Filtering function on Streams
function main(stream) {
try {
// Aave V3 LiquidationCall ABI
const aaveLiquidationAbi = `[{
"anonymous": false,
"inputs": [
{"indexed": true, "type": "address", "name": "collateralAsset"},
{"indexed": true, "type": "address", "name": "debtAsset"},
{"indexed": true, "type": "address", "name": "user"},
{"indexed": false, "type": "uint256", "name": "debtToCover"},
{"indexed": false, "type": "uint256", "name": "liquidatedCollateralAmount"},
{"indexed": false, "type": "address", "name": "liquidator"},
{"indexed": false, "type": "bool", "name": "receiveAToken"}
],
"name": "LiquidationCall",
"type": "event"
}]`;
// Pool.sol addresses for Aave V3 (lowercase)
const normalizedAddresses = ["0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"];
// Handle both payload and stream parameter styles
const data = stream?.data ? stream.data : stream;
// Early validation - return null to skip (per Quicknode docs)
if (!data?.[0]?.block?.timestamp || !data?.[0]?.receipts?.length) {
return null;
}
const timestamp = parseInt(data[0].block.timestamp, 16);
// Decode receipts
const decodedReceipts = decodeEVMReceipts(data[0].receipts, [aaveLiquidationAbi]);
// Validate decoded receipts
if (!decodedReceipts || !Array.isArray(decodedReceipts) || decodedReceipts.length === 0) {
return null;
}
// Filter for liquidation events
const filteredLogs = decodedReceipts
.filter(r => r?.decodedLogs?.length > 0)
.flatMap(r => r.decodedLogs)
.filter(log =>
log?.name === "LiquidationCall" &&
log?.address &&
normalizedAddresses.includes(log.address.toLowerCase())
);
// Return data if found, null otherwise (per Quicknode docs)
return filteredLogs?.length > 0
? { timestamp, filteredLogs }
: null;
} catch (e) {
// Return null to skip on error (per Quicknode docs)
return null;
}
}
Quicknode
- Log in op je Quicknode of maak een gratis proefaccount aan.
- Maak een nieuw Endpoint aan Endpoint je blockchain en netwerk (bijvoorbeeldMainnet).
- Kopieer de RPC-URL en plak deze in de
QUICKNODEvariabele in de.envbestand.
Database instellen
- Log in op je Supabase-dashboard.
- Ga naar je project en klik op de knop ‘Verbinden ’.
- Klik onder ‘Verbindingsgegevens’ op ‘App-frameworks ’ en stel ‘Framework’ in op ‘React ’ en ‘Vite -framework gebruiken’.
- Kopiëren
VITE_SUPABASE_URLenVITE_SUPABASE_PUBLISHABLE_DEFAULT_KEYnaar.env.
PostgreSQL-functies instellen
Om de gegevens uit de database op te halen, kun je de volgende PostgreSQL-query’s gebruiken om een aantal functies te maken. Deze functies worden in de frontend gebruikt om de gegevens weer te geven.
Code van PostgreSQL-functies bekijken
-- get_asset_distributions
CREATE OR REPLACE FUNCTION get_asset_distributions(time_range text)
RETURNS json AS $$
DECLARE
interval_start timestamp;
BEGIN
interval_start := CASE time_range
WHEN '24h' THEN NOW() - INTERVAL '24 hours'
WHEN '7d' THEN NOW() - INTERVAL '7 days'
WHEN '30d' THEN NOW() - INTERVAL '30 days'
WHEN '365d' THEN NOW() - INTERVAL '365 days'
ELSE NOW() - INTERVAL '30 days'
END;
RETURN json_build_object(
'topCollateralAssets', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT
collateral_asset_symbol as symbol,
COUNT(*) as count,
SUM(collateral_seized_amount * collateral_asset_price) as totalValueUSD,
SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)) as totalProfitUSD,
(COUNT(*)::float / (SELECT COUNT(*) FROM liquidations WHERE timestamp >= interval_start)::float * 100) as percentageOfTotal
FROM liquidations
WHERE timestamp >= interval_start
GROUP BY collateral_asset_symbol
ORDER BY count DESC
LIMIT 5
) t
),
'topDebtAssets', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT
debt_asset_symbol as symbol,
COUNT(*) as count,
SUM(debt_repaid_amount * debt_asset_price) as totalValueUSD,
SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)) as totalProfitUSD,
(COUNT(*)::float / (SELECT COUNT(*) FROM liquidations WHERE timestamp >= interval_start)::float * 100) as percentageOfTotal
FROM liquidations
WHERE timestamp >= interval_start
GROUP BY debt_asset_symbol
ORDER BY count DESC
LIMIT 5
) t
)
);
END;
$$ LANGUAGE plpgsql;
-- get_metrics_overview
CREATE OR REPLACE FUNCTION get_metrics_overview()
RETURNS json AS $$
BEGIN
RETURN json_build_object(
'total24h', (
SELECT json_build_object(
'count', COUNT(*),
'valueUSD', COALESCE(SUM(debt_repaid_amount * debt_asset_price), 0),
'profitUSD', COALESCE(SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)), 0)
)
FROM liquidations
WHERE timestamp >= NOW() - INTERVAL '24 hours'
),
'total7d', (
SELECT json_build_object(
'count', COUNT(*),
'valueUSD', COALESCE(SUM(debt_repaid_amount * debt_asset_price), 0),
'profitUSD', COALESCE(SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)), 0)
)
FROM liquidations
WHERE timestamp >= NOW() - INTERVAL '7 days'
),
'total30d', (
SELECT json_build_object(
'count', COUNT(*),
'valueUSD', COALESCE(SUM(debt_repaid_amount * debt_asset_price), 0),
'profitUSD', COALESCE(SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)), 0)
)
FROM liquidations
WHERE timestamp >= NOW() - INTERVAL '30 days'
),
'total365d', (
SELECT json_build_object(
'count', COUNT(*),
'valueUSD', COALESCE(SUM(debt_repaid_amount * debt_asset_price), 0),
'profitUSD', COALESCE(SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)), 0)
)
FROM liquidations
WHERE timestamp >= NOW() - INTERVAL '365 days'
),
'topLiquidators', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT
liquidator_address as address,
COUNT(*) as count,
SUM(debt_repaid_amount * debt_asset_price) as totalValueUSD,
AVG(debt_repaid_amount * debt_asset_price) as avgLiquidationUSD,
SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)) as totalProfitUSD,
AVG((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)) as avgProfitUSD
FROM liquidations
GROUP BY liquidator_address
ORDER BY totalProfitUSD DESC
LIMIT 5
) t
),
'topLiquidatedUsers', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT
liquidated_wallet as address,
COUNT(*) as count,
SUM(debt_repaid_amount * debt_asset_price) as totalValueUSD,
AVG(debt_repaid_amount * debt_asset_price) as avgLiquidationUSD,
SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)) as totalLossUSD,
AVG((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)) as avgLossUSD
FROM liquidations
GROUP BY liquidated_wallet
ORDER BY count DESC
LIMIT 5
) t
),
'largestLiquidations', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT
transaction_hash as txHash,
timestamp,
debt_repaid_amount * debt_asset_price as valueUSD,
(collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price) as profitUSD,
collateral_asset_symbol as collateralAsset,
debt_asset_symbol as debtAsset,
liquidator_address as liquidator,
liquidated_wallet as liquidatedUser
FROM liquidations
ORDER BY debt_repaid_amount * debt_asset_price DESC
LIMIT 5
) t
),
'mostProfitableLiquidations', (
SELECT json_agg(row_to_json(t))
FROM (
SELECT
transaction_hash as txHash,
timestamp,
debt_repaid_amount * debt_asset_price as valueUSD,
(collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price) as profitUSD,
collateral_asset_symbol as collateralAsset,
debt_asset_symbol as debtAsset,
liquidator_address as liquidator,
liquidated_wallet as liquidatedUser
FROM liquidations
ORDER BY ((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)) DESC
LIMIT 5
) t
)
);
END;
$$ LANGUAGE plpgsql;
-- get_liquidation_trends
CREATE OR REPLACE FUNCTION get_liquidation_trends(time_range text)
RETURNS json AS $$
DECLARE
interval_start timestamp;
grouping_interval text;
BEGIN
SELECT
CASE time_range
WHEN '24h' THEN NOW() - INTERVAL '24 hours'
WHEN '7d' THEN NOW() - INTERVAL '7 days'
WHEN '30d' THEN NOW() - INTERVAL '30 days'
WHEN '365d' THEN NOW() - INTERVAL '365 days'
ELSE NOW() - INTERVAL '30 days'
END,
CASE time_range
WHEN '24h' THEN 'hour'
WHEN '7d' THEN 'day'
WHEN '30d' THEN 'day'
WHEN '365d' THEN 'month'
ELSE 'day'
END
INTO interval_start, grouping_interval;
RETURN (
SELECT json_agg(row_to_json(t))
FROM (
SELECT
date_trunc(grouping_interval, timestamp) as date,
COUNT(*) as count,
COALESCE(SUM(debt_repaid_amount * debt_asset_price), 0) as totalValueUSD,
COALESCE(SUM((collateral_seized_amount * collateral_asset_price) - (debt_repaid_amount * debt_asset_price)), 0) as totalProfitUSD
FROM liquidations
WHERE timestamp >= interval_start
GROUP BY date_trunc(grouping_interval, timestamp)
ORDER BY date_trunc(grouping_interval, timestamp)
) t
);
END;
$$ LANGUAGE plpgsql;
PostgreSQL-indexen instellen
Om de prestaties van zoekopdrachten te verbeteren, kunnen we indexen aanmaken op de liquidaties tabel. Hier volgt een voorbeeld van hoe je de benodigde indexen kunt aanmaken:
MAAK INDEX AANMAKEN idx_debt_asset OP liquidaties(schuld-actief_symbool tekst_ops);
CREATE INDEX idx_timestamp OP liquidaties(tijdstempel timestamp_ops);
CREATE INDEX idx_liquidator_address OP liquidaties(liquidator_address text_ops);
CREATE INDEX idx_liquidated_wallet OP liquidaties(liquidated_wallet text_ops);
CREATE INDEX idx_collateral_asset OP liquidaties(onderpand_activum_symbool tekst_ops);
CREATE INDEX idx_liquidations_assets OP liquidations(collateral_asset_symbol text_ops,schuld_activum_symbool text_ops);
Ontwikkeling
Projectopbouw
src/
├── components/ # Herbruikbare UI-componenten
├── hooks/ # Aangepaste React-hooks
├── lib/
│ ├── security.ts # Valideren Streams binnenkomende Streams
│ ├── server.ts # Onze webhook-server
│ ├── supabase-backend.ts # Supabase-sleutels voor backend-bewerkingen
│ ├── supabase-frontend.ts # Supabase-sleutels voor frontend-bewerkingen
│ ├── transform.ts # Gegevenstransformatie van binnenkomende Streams
│ └── utils.ts # Algemene hulpprogrammafuncties
└── types/ # TypeScript-typedefinities
Beschikbare scripts
npm run dev: Start de ontwikkelingsservernpm run build: Bouwen voor productienpm run server: Webhook-server startennpm run lint: ESLint uitvoeren
Mogelijke toekomstige functies
Geavanceerde functies
- Tracking van Whale-wallets
- Aangepaste waarschuwingsdrempels
- Beoordeling van het portefeuillerisico
- Integratie met andere DeFi-protocollen
- Realtime meldingen voor:
- Grote liquidaties
- Specifieke activa
- Waarschuwingen bij prijsdrempels
Technische verbeteringen
- Ondersteuning voor WebSocket voor realtime updates
- Ondersteuning voor meerdere netwerken (Polygon, Arbitrum, enz.)
- Geavanceerde opties voor gegevensvisualisatie
- Versie voor mobiele apps
- API-toegang voor ontwikkelaars
- Maak een fork van de repository
- Maak een feature-branch aan:git checkout -b feature/amazing-feature
- Sla je wijzigingen op:git commit -m "Geweldige functie toevoegen"
- Pusht je branch:git push origin feature/amazing-feature
- Dien een pull-verzoek in.

