Drücken Sie ESC, um zu schließen

Anti-MEV Stealth Swap: Final Showdown – Versand & CLI

Nun gut, in den vorangegangenen zwei Teilen haben wir den „sicheren Kommunikationskanal“ aufgebaut und die Swap-Logik geschrieben. In diesem Kapitel schließen wir den Bau des Tools ab. Wir implementieren die Warteschleife für die Blöcke und verpacken das Ganze in ein praktisches Command Line Interface (CLI).

1. Versand-Logik: Warum man nicht einfach auf „Senden“ klicken kann

Das Ethereum-Netzwerk generiert etwa alle 12 Sekunden einen Block. Dein Bundle (das Transaktionspaket) ist nur für eine ganz bestimmte Blocknummer gültig. Wenn es nicht in den nächsten Block aufgenommen wird (z. B. wegen eines zu niedrigen Trinkgelds), muss es für den nächsten Block mit einer aktualisierten Nummer neu erstellt werden.

Wir verwenden eine Schleife, die versuchen wird, unseren Stealth Swap über die nächsten 10 Blöcke „durchzudrücken“.

 

2. Code: Implementierung des Versands und des CLI

Fügen wir die finale Methode sendBundle und eine einfache Verarbeitung von Terminal-Argumenten hinzu.

import { FlashbotsBundleResolution } from "@flashbots/ethers-provider-bundle";

async function runStealthSwap(amountInEth: string) {
    const { wallet, flashbotsProvider, provider } = await initStealthProvider();
    const amountIn = ethers.parseEther(amountInEth);
    let currentBlock = await provider.getBlockNumber();

    console.log(`Start bei Block: ${currentBlock}`);

    // Wir versuchen, das Bundle innerhalb der nächsten 10 Blöcke zu senden
    for (let i = 0; i < 10; i++) {
        const targetBlock = currentBlock + i;
        
        // Bundle für den spezifischen Block neu erstellen (inklusive Simulation)
        const signedBundle = await createAndSimulateBundle(wallet, flashbotsProvider, provider, amountIn, targetBlock);
        
        if (!signedBundle) continue;

        const bundleSubmission = await flashbotsProvider.sendBundle(signedBundle, targetBlock);
        
        if ("error" in bundleSubmission) {
            console.error(`Versandfehler: ${bundleSubmission.error.message}`);
            continue;
        }

        console.log(`Bundle gesendet. Warte auf Block ${targetBlock}...`);
        const waitResponse = await bundleSubmission.wait();
        
        if (waitResponse === FlashbotsBundleResolution.BundleIncluded) {
            console.log(`SIEG! Transaktion wurde in Block ${targetBlock} aufgenommen`);
            console.log(`Hash: https://etherscan.io/tx/${(await signedBundle)[0].hash}`); // Ungefährer Hash
            return;
        } else if (waitResponse === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
            console.log(`Fehlschlag. Block ${targetBlock} ist ohne uns vergangen. Nächster Versuch...`);
        } else if (waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
            console.error("Fehler: Nonce zu hoch. Überprüfe ausstehende Transaktionen.");
            return;
        }
    }
}

// Einfaches CLI-Interface
const amount = process.argv[2] || "0.01";
runStealthSwap(amount);

Natürlich ist das, was wir bisher gemacht haben, nur ein „Flashbots Hello World“ für Swaps auf Uniswap V3. Damit das Ganze unter realen Bedingungen Hand und Fuß hat, müssen wir es noch etwas verfeinern.

 

import { ethers } from "ethers";
import { FlashbotsBundleProvider, FlashbotsBundleResolution } from "@flashbots/ethers-provider-bundle";
import * as dotenv from "dotenv";

dotenv.config();

const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const ROUTER = "0xE592427A0AEce92De3Edee1F18E0157C05861564";
const QUOTER = "0x61fFe014bA17989E743c5F6cB21bF9697530B21e";

const ABI = [
    "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut)",
    "function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)",
    "function approve(address spender, uint256 amount) external returns (bool)",
    "function allowance(address owner, address spender) external view returns (uint256)",
    "function balanceOf(address account) external view returns (uint256)"
];

async function main() {
    const provider = new ethers.JsonRpcProvider(process.env.ETH_RPC_URL);
    const wallet = new ethers.Wallet(process.env.SENDER_PRIVATE_KEY, provider);
    const authSigner = new ethers.Wallet(process.env.FLASHBOTS_AUTH_KEY, provider);
    const flashbots = await FlashbotsBundleProvider.create(provider, authSigner);

    const quoter = new ethers.Contract(QUOTER, ABI, provider);
    const weth = new ethers.Contract(WETH, ABI, wallet);

    const baseAmount = ethers.parseEther(process.argv[2] || "0.1");
    const minThreshold = ethers.parseUnits(process.argv[3] || "200", 6);

    // --- PRECHECK ---
    const balance = await weth.balanceOf(wallet.address);
    if (balance < baseAmount) throw new Error("NICHT GENUG WETH");

    const allowance = await weth.allowance(wallet.address, ROUTER);
    if (allowance < baseAmount) {
        await (await weth.approve(ROUTER, ethers.MaxUint256)).wait();
    }

    let nonce = await wallet.getNonce();
    let lastQuote = 0n;
    let lastExecutionBlock = 0;

    provider.on("block", async (block) => {
        try {
            // --- RANDOM SKIP (bricht Verhaltensmuster auf) ---
            if (Math.random() < 0.6) return;

            const quote = await quoter.quoteExactInputSingle.staticCall({
                tokenIn: WETH,
                tokenOut: USDC,
                amountIn: baseAmount,
                fee: 3000,
                sqrtPriceLimitX96: 0
            });

            // --- THRESHOLD (Rentabilitätsschwelle) ---
            if (quote.amountOut < minThreshold) return;

            // --- ÄNDERUNGSERKENNUNG ---
            if (lastQuote !== 0n) {
                const diff = (quote.amountOut * 1000n) / lastQuote;
                // Ignorieren, wenn Änderung < 0.3%
                if (diff > 997n && diff < 1003n) return;
            }

            // --- COOL DOWN ---
            if (block - lastExecutionBlock < 2) return;

            // --- RANDOM AMOUNT (bricht die Transaktionssignatur auf) ---
            const randomFactor = BigInt(95 + Math.floor(Math.random() * 10)); // 95–105%
            const amountIn = (baseAmount * randomFactor) / 100n;

            // --- SLIPPAGE ---
            const minOut = (quote.amountOut * 995n) / 1000n;

            const feeData = await provider.getFeeData();
            const tx = {
                to: ROUTER,
                data: new ethers.Interface(ABI).encodeFunctionData("exactInputSingle", [{
                    tokenIn: WETH,
                    tokenOut: USDC,
                    fee: 3000,
                    recipient: wallet.address,
                    deadline: Math.floor(Date.now() / 1000) + 90,
                    amountIn: amountIn,
                    amountOutMinimum: minOut,
                    sqrtPriceLimitX96: 0
                }]),
                chainId: 1,
                type: 2,
                gasLimit: 250000,
                maxFeePerGas: feeData.maxFeePerGas,
                maxPriorityFeePerGas: feeData.maxPriorityFeePerGas || 2n,
                nonce: nonce
            };

            const signed = await flashbots.signBundle([{ signer: wallet, transaction: tx }]);
            const target = block + 1;

            const sim = await flashbots.simulate(signed, target);
            if ("error" in sim) return;

            const sub = await flashbots.sendBundle(signed, target);
            if ("error" in sub) return;

            const res = await sub.wait();
            nonce++; // WICHTIG
            if (res === FlashbotsBundleResolution.BundleIncluded) {
                console.log("AUSGEFÜHRT IN BLOCK:", target);
                process.exit(0);
            }

            lastQuote = quote.amountOut;
            lastExecutionBlock = block;
        } catch (e) {
            // Stiller Modus bei Fehlern
        }
    });
}

main();

 

3. So verwendest du den Anti-MEV Stealth Swap

1. Installation

npm install ethers @flashbots/ethers-provider-bundle dotenv

2. Erstelle eine .env-Datei

ETH_RPC_URL=https://mainnet.infura.io/v3/DEIN_KEY
SENDER_PRIVATE_KEY=DEIN_PRIVATE_KEY
FLASHBOTS_AUTH_KEY=IRGENDEIN_NEUER_PRIVATE_KEY

3. Wallet-Vorbereitung

Notwendig:

  • Du musst Ethereum (ETH) auf der Wallet haben → für die Gas-Gebühren.
  • Sowie Wrapped Ether (WETH) → für den eigentlichen Tausch.

Falls du kein WETH hast:

  • Tausche zuerst ETH in WETH über eine beliebige Seite (z. B. Uniswap).

4. Starten

node app.js 0.1 200

Dabei gilt:

  • 0.1 → wie viel WETH du tauschen möchtest.
  • 200 → die minimal akzeptable Menge an USDC am Ausgang.

5. Wie funktioniert es unter der Haube?

Nach dem Start:

  • Prüft den WETH-Stand.
  • Führt „Approve“ aus (nur einmal).
  • Beginnt, auf neue Blöcke zu warten.
  • In jedem Block:
    • Lässt absichtlich manchmal einen Block aus (Zufallsprinzip).
    • Prüft den Preis über den Quoter.
    • Überprüft deine Rentabilitätsschwelle.
    • Prüft, ob sich der Preis bewegt hat (vermeidet Stagnation).
    • Modifiziert leicht den Transaktionsbetrag (zur Tarnung).
    • Führt eine Simulation durch.
    • Sendet über Flashbots.

6. Wann kommt es zur Transaktion?

  • Preis ≥ angegebene Schwelle.
  • Preis hat sich geändert.
  • Cooldown-Zeit ist abgelaufen.
  • Simulation war erfolgreich.

7. Ergebnis

Wenn du in der Konsole Folgendes siehst:

EXECUTED: 19483921

Bedeutet das:

  • Der Swap war erfolgreich.
  • Das Skript hat seine Arbeit beendet.

8. Wichtige Parameter
Schwelle (zweites Argument)

node app.js 0.1 220

Höhere Schwelle =

  • Weniger Transaktionen.
  • Besserer Ausführungspreis.

Betrag

node app.js 0.05 200

Kleinerer Betrag =

  • Geringerer Einfluss auf den Markt (Price Impact).
  • Niedrigeres Risiko.

9. Was man beachten muss

  • Dies garantiert nicht immer den absolut besten Preis der Welt.
  • Es schützt nicht vor absolut jeder Art von Angriff.
  • Aber es senkt drastisch die Chance, dass dich jemand „rasiert“ (Front-run).

10. Wann man es NICHT verwenden sollte

  • Wenn du nicht verstehst, wie Slippage funktioniert.
  • Wenn du zu wenig Mittel hast, um die Gas-Kosten zu decken.
  • Wenn das Netzwerk komplett verstopft ist.

Zusammenfassung

  • Start mit einem Befehl.
  • Funktioniert automatisch.
  • Führt einen „leisen“ Swap über Flashbots durch.

Dieser Code:

  • Wird im direkten Duell nicht gegen professionelle MEV-Bots gewinnen.
  • Wird dich nicht in jedem Szenario absichern.
  • Gibt keinen magischen Vorteil (Edge).

ABER:
👉 Du bist keine leichte Beute mehr.

 

Dieses Tool ist nur die Spitze des Eisbergs. Man kann es für Arbitrage, Liquidationen oder das sichere Verschieben großer Liquiditätsmengen ausbauen.

Das Prinzip ist einfach: Im „dunklen Wald“ der Blockchain gewinnt nicht der, der am lautesten schreit, sondern der, der es versteht, sich lautlos anzupirschen.

Material vorbereitet für die EXMON Academy. Experimentiert im Mainnet vorsichtig und achtet immer auf eure Slippage-Einstellungen!

Sying Yu

I am a blockchain developer specializing in building secure, scalable, and innovative decentralized solutions. My expertise covers smart contracts, payment systems, and integrating crypto with fiat to optimize financial workflows. I thrive on creating modern, efficient tools for the evolving digital economy....

Diskussion beitreten

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *