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 dotenv2. 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_KEY3. 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 200Dabei 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: 19483921Bedeutet das:
- Der Swap war erfolgreich.
- Das Skript hat seine Arbeit beendet.
8. Wichtige Parameter
Schwelle (zweites Argument)
node app.js 0.1 220Höhere Schwelle =
- Weniger Transaktionen.
- Besserer Ausführungspreis.
Betrag
node app.js 0.05 200Kleinerer 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!