Token-, NFT- und Smart-Contract-Flüsse · Lektion 4 von 6
DEX-Swaps und Liquiditätsflüsse
8 Min. Lesezeit
Dezentrale Börsen sind der Ort, an dem gestohlene Token in ETH oder Stablecoins umgewandelt werden, an dem Rug Pulls am Tag des Token-Launches die Käufer ausnehmen und an dem Bots gewöhnlichen Tradern Wert abschöpfen. Jeder Swap hinterlässt präzise Belege: das eigene Ereignis des Pools, die Token-Transfers darum herum und die Reserven des Pools davor und danach. Diese Lektion zeigt, wie man diese Belege liest, nachvollzieht, was ein Trader über einen Aggregator tatsächlich getan hat, die Preisauswirkung misst und MEV-Muster erkennt.
Pools, token0 und token1
Ein Paar im Stil von Uniswap v2 ist ein Vertrag, der zwei Token hält. Der Token mit der niedrigeren Adresse ist token0, der andere token1; diese Reihenfolge steht bei der Erstellung fest und hat nichts damit zu tun, welcher Token „der Preis“ ist. Jedes Ereignis meldet die Beträge in dieser Reihenfolge. Daher muss ein Decoder token0, token1 und die Dezimalstellen beider Token nachschlagen, bevor er etwas umrechnet.
Das Swap-Ereignis von v2 lautet Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to), topic0 0xd78ad95f…d822. Nach jeder Änderung der Reserven gibt das Paar außerdem Sync(uint112 reserve0, uint112 reserve1) mit den neuen Guthaben aus. Ein decodierter Swap in einem Paar, bei dem token0 USDC (6 Dezimalstellen) und token1 WETH (18 Dezimalstellen) ist:
{
"event": "Swap",
"pair": "0x3f8a…e2d1",
"sender": "0x7a2c…48d0",
"to": "0xa1b2c3d4…9f0e",
"amount0In": "0",
"amount1In": "5000000000000000000",
"amount0Out": "14880819116",
"amount1Out": "0",
"next_log": {"event": "Sync", "reserve0": "2985119180884", "reserve1": "1005000000000000000000"}
}
So ist das zu lesen: 5 WETH gingen hinein und 14.880,819116 USDC kamen heraus. Der Ausführungskurs beträgt 14.880,82 ÷ 5 = 2.976,16 USDC pro WETH. Der sender ist üblicherweise ein Router-Vertrag und nicht der Trader; to ist die Adresse, an die der Output ging. Der Trader ist die Person, die letztlich den Input gesendet und den Output erhalten hat. Das lässt sich anhand der Token-Transfers in derselben Transaktion bestätigen.
Pools mit konzentrierter Liquidität und andere Designs
Uniswap-v3-Pools geben Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick) aus, topic0 0xc42079f9…ca67. Die Beträge sind aus Sicht des Pools vorzeichenbehaftet: Ein positiver Betrag bedeutet, dass der Token in den Pool hineinging; ein negativer, dass er den Pool verlassen hat. Ein Swap von 2 WETH gegen USDC in einem USDC/WETH-Pool zeigt amount0 = -5994120000 (5.994,12 USDC hinaus) und amount1 = 2000000000000000000 (2 WETH hinein).
Das Ereignis gibt auch den Poolkurs nach dem Swap an, der als Quadratwurzel im Festkommaformat codiert ist:
price_raw = (sqrtPriceX96 / 2^96)^2 -- Basiseinheiten von token1 pro Basiseinheit von token0
price = price_raw * 10^(decimals0 - decimals1)
example : sqrtPriceX96 = 1446501726624926496477173928747177
price_raw = 333.333.333,33 -> price = 0,000333333 WETH pro USDC
inverse = 3.000 USDC pro WETH (tick ≈ 196.256, da price_raw = 1.0001^tick)
Der quadrierte Wert ist in Basiseinheiten angegeben, daher ist die Anpassung der Dezimalstellen (6 − 18 = −12) unerlässlich. Wird sie ausgelassen, weicht der Kurs um den Faktor 10^12 ab.
Andere Designs benötigen eigene Decoder. Uniswap v4 (2025) hält alle Pools innerhalb eines einzigen Singleton-Vertrags PoolManager. Daher identifiziert die address des Logs den Pool nicht mehr; maßgeblich ist die indizierte Pool-id. Das Swap-Ereignis Swap(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee) (topic0 0x40e9cecb…112f) versieht Beträge aus Sicht des Nutzers mit Vorzeichen, also entgegengesetzt zu v3, wie der Datenleitfaden der Uniswap Foundation warnt. Curve-Pools geben TokenExchange mit Coin-Indizes statt Token-Adressen aus. Verwenden Sie für jedes Protokoll und jede Version einen eigenen Decoder und testen Sie ihn anhand einer Transaktion, deren Ergebnis bekannt ist.
Routing über Aggregatoren
Ein Aggregator teilt einen Auftrag in einer einzigen Transaktion auf Pools und Poolketten auf. Die Pool-Ereignisse zeigen jeden Teilweg; die Token-Transfers zeigen das Nettoergebnis des Traders. Eine decodierte Route, bei der der Router zwischen den Teilwegen Guthaben hält:
| logIndex | Token | Von | An | Betrag |
|---|---|---|---|---|
| 10 | WETH | Trader | Router | 10 |
| 11 | WETH | Router | Pool A (WETH/USDC) | 6 |
| 12 | USDC | Pool A | Router | 17.880 |
| 13 | WETH | Router | Pool B (WETH/DAI) | 4 |
| 14 | DAI | Pool B | Pool C (DAI/USDC) | 11.930 |
| 15 | USDC | Pool C | Router | 11.925 |
| 16 | USDC | Router | Trader | 29.805 |
Wenn man die Zu- und Abgänge jeder Adresse saldiert, lässt sich dies zu einer Aussage zusammenfassen: Der Trader verkaufte 10 WETH für 29.805 USDC, also durchschnittlich 2.980,50 USDC pro WETH. Beim Router und den Zwischenpools ergibt sich netto null oder eine eigene Pooländerung.
-- Netto-Tokenänderung pro Adresse innerhalb einer Transaktion
SELECT address, token_contract, SUM(delta) AS net_raw
FROM (
SELECT to_address AS address, token_contract, value_raw AS delta
FROM token_transfers WHERE tx_hash = '0x9e1d…b7a4'
UNION ALL
SELECT from_address, token_contract, -value_raw
FROM token_transfers WHERE tx_hash = '0x9e1d…b7a4'
) t
GROUP BY address, token_contract
HAVING SUM(delta) <> 0;
Die Abfrage addiert für jede Adresse und jeden Token alle eingehenden Transfers und zieht alle ausgehenden ab. Sie behält nur Ergebnisse ungleich null bei. Native ETH-Transfers erscheinen nicht in den Token-Transfers und müssen anhand von Traces ergänzt werden. Bei intentbasierten Systemen wird die Transaktion von einem Solver gesendet, nicht vom Nutzer. Daher bezeichnet tx.from den Solver; den Nutzer findet man anhand der Trade-Ereignisse des Settlement-Vertrags und der Transfers zu und von der Adresse des Nutzers.
Liquidität hinzufügen und entfernen
Liquiditätsereignisse zeigen, wer einen Markt finanziert und wer Liquidität abzieht.
| Protokoll | Hinzufügen | Entfernen | Eigentumsnachweis |
|---|---|---|---|
| Uniswap v2 | Mint(sender, amount0, amount1) plus LP-Token-Transfer von der Nulladresse | LP-Token an das Paar gesendet, Burn(sender, amount0, amount1, to), LP-Transfer an die Nulladresse | Fungibler LP-Token (der Paarvertrag) |
| Uniswap v3 | Pool-Mint mit Tick-Bereich; IncreaseLiquidity auf dem Positionsmanager | Pool-Burn (schreibt Token gut), dann Collect (zahlt sie aus); DecreaseLiquidity | Positions-NFT (ERC-721) |
| Uniswap v4 | ModifyLiquidity im PoolManager | Dasselbe Ereignis, negativer Liquiditätsdelta | Positionsdatensätze, oft ein NFT |
Zwei Fallstricke: Bei v3 weist das Mint des Pools den Positionsmanager-Vertrag als Eigentümer aus. Der tatsächliche Liquiditätsanbieter ist jedoch der Inhaber des Positions-NFT; folgen Sie dem NFT. Und bei einem v3-Burn werden keine Token bewegt; das Geld fließt erst mit Collect ab. Ein klassisches Muster für einen Rug Pull ist ein Bereitsteller, der einen Token erstellt, Liquidität hinzufügt, auf Käufer wartet und dann nahezu die gesamte Liquidität in einer Transaktion entfernt. Die Beträge bei Burn oder DecreaseLiquidity zeigen, wie viel abgezogen wurde.
Preisauswirkung berechnen
Die Preisauswirkung vergleicht den Ausführungskurs mit dem Spotkurs des Pools unmittelbar vor dem Trade. Bei einem v2-Paar entsprechen die Reserven vor dem Swap den Reserven im Sync-Ereignis minus den Inputs plus den Outputs: Hier ergeben 2.985.119,18 + 14.880,82 = 3.000.000 USDC und 1.005 − 5 = 1.000 WETH einen Spotkurs von 3.000.
| Messgröße | Wert |
|---|---|
| Spotkurs davor | 3.000,00 USDC pro WETH |
| Ausführungskurs | 2.976,16 (−0,79 %) |
| Output ohne Gebühr von 0,30 % | 14.925,37, Kurs 2.985,07 (−0,50 %) |
Spotkurs danach (aus Sync) | 2.970,27 (−0,99 %) |
Von der Abweichung von 0,79 % entfallen also etwa 0,30 % auf die Gebühr und etwa 0,50 % auf die reine Preisauswirkung. Der Spotkurs nach dem Trade hat sich doppelt so stark bewegt wie der durchschnittlich bezahlte Kurs. Bei Pools mit konstantem Produkt ist das normal. Eine starke Preisauswirkung in einem Pool mit geringer Liquidität ist ein Anzeichen für erzwungene Verkäufe, etwa wenn ein Angreifer einen gestohlenen Token abstößt.
MEV- und Sandwich-Muster erkennen
Aktivitäten mit maximal extrahierbarem Wert (MEV) weisen in Blockdaten erkennbare Muster auf:
- Sandwich: In einem Block und einem Pool kauft Akteur X Token T, danach kauft ein Opfer T und unmittelbar darauf verkauft X T wieder. Die drei Transaktionen liegen normalerweise direkt hintereinander im Index, die von X verkaufte Menge entspricht ungefähr der gekauften, und X zahlt dem Block-Builder oft direkt (eine ETH-Überweisung an den Gebührenempfänger des Blocks, sichtbar in Traces).
- Atomare Arbitrage: Eine Transaktion tauscht Token über einen Poolzyklus (WETH → USDC → DAI → WETH) und endet mit mehr Einheiten des Ausgangstokens.
- Just-in-time-(JIT-)Liquidität: Ein v3-
Mintunmittelbar vor einem großen Swap und einBurnundCollectunmittelbar danach, jeweils von derselben Adresse. - Liquidationen: Behandelt in der Lektion Kreditvergabe, Bridges und Protokollbilanzierung.
-- Sandwich-Kandidaten: derselbe Block und Pool, eröffnet und geschlossen von einem Akteur
SELECT f.block_number, f.pool, f.actor, v.tx_hash AS victim_tx
FROM dex_swaps f
JOIN dex_swaps v
ON v.block_number = f.block_number AND v.pool = f.pool
AND v.tx_index > f.tx_index AND v.token_bought = f.token_bought
JOIN dex_swaps b
ON b.block_number = f.block_number AND b.pool = f.pool
AND b.tx_index > v.tx_index AND b.actor = f.actor
AND b.token_sold = f.token_bought
WHERE v.actor <> f.actor;
Die Abfrage sucht nach einem Front-Run (f), einem Opfer (v), das später im selben Block denselben Token kauft, und einem Back-Run (b), bei dem derselbe Akteur den Token verkauft. Definieren Sie actor als den Bot-Vertrag, der den Swap-Output erhalten hat, und nicht als Absender der Transaktion, da Sandwich-Bots häufig wechselnde Sendeadressen verwenden. Betrachten Sie Treffer als Kandidaten: Prüfen Sie die Mengen, den Gewinn und ob eine harmlose Erklärung fehlt, etwa zwei unabhängige Arbitrageure.
Prüfungsschwerpunkt: Reihenfolge von token0/token1 und Dezimalstellen; Decodierung der Uniswap-v2-Swap- (amountIn/amountOut) undSync-Ereignisse; vorzeichenbehaftete Beträge bei v3 (positiv bedeutet in den Pool hinein) und Umrechnung von sqrtPriceX96 in einen Kurs mit Anpassung der Dezimalstellen; der Singleton PoolManager, die Pool-ID und die umgekehrte Vorzeichenkonvention bei v4; Saldierung von Transfers, um das Ergebnis eines Traders über einen Aggregator zu ermitteln, und warum tx.from ein Router oder Solver sein kann; Liquiditätsereignisse bei v2 und v3, Positions-NFTs und das Muster Burn-then-Collect; Berechnung der Preisauswirkung anhand der Reserven; typische Merkmale von Sandwiches, atomarer Arbitrage und JIT-Liquidität sowie falsch positive Treffer.