Naciśnij ESC, aby zamknąć

Anti-MEV Stealth Swap: Ostateczne starcie – Wysyłka i CLI

No dobra, w poprzednich dwóch częściach ogarnęliśmy „bezpieczny kanał łączności” i napisaliśmy logikę swapu. W tym rozdziale kończymy budowę narzędzia. Zaimplementujemy pętlę oczekiwania na blok i zapakujemy to wszystko w wygodny interfejs wiersza poleceń (CLI).

1. Logika wysyłki: Dlaczego nie można po prostu kliknąć „Send”?

Sieć Ethereum generuje bloki mniej więcej co 12 sekund. Twój bundle (paczka transakcji) jest ważny tylko dla konkretnego numeru bloku. Jeśli nie załapie się do najbliższego (np. przez zbyt niski napiwek), trzeba go złożyć na nowo dla kolejnego bloku z zaktualizowanym numerem.

Wykorzystamy pętlę, która będzie próbowała „przepchnąć” nasz Stealth Swap przez kolejne 10 bloków.

 

2. Kod: Implementacja wysyłki i CLI

Dodajmy finałową metodę sendBundle i prostą obsługę argumentów z terminala.

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 na bloku: ${currentBlock}`);

    // Próbujemy wysłać bundle w ciągu najbliższych 10 bloków
    for (let i = 0; i < 10; i++) {
        const targetBlock = currentBlock + i;
        
        // Składamy bundle na nowo dla konkretnego bloku (wraz z symulacją)
        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(`Błąd wysyłki: ${bundleSubmission.error.message}`);
            continue;
        }

        console.log(`Bundle wysłany. Czekamy na blok ${targetBlock}...`);
        const waitResponse = await bundleSubmission.wait();
        
        if (waitResponse === FlashbotsBundleResolution.BundleIncluded) {
            console.log(`ZWYCIĘSTWO! Transakcja weszła do bloku ${targetBlock}`);
            console.log(`Hash: https://etherscan.io/tx/${(await signedBundle)[0].hash}`); // Przybliżony hash
            return;
        } else if (waitResponse === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
            console.log(`Pudło. Blok ${targetBlock} przeszedł bez nas. Próbujemy dalej...`);
        } else if (waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
            console.error("Błąd: Nonce za wysoki. Sprawdź oczekujące transakcje.");
            return;
        }
    }
}

// Prosty interfejs CLI
const amount = process.argv[2] || "0.01";
runStealthSwap(amount);

Oczywiście to, co zrobiliśmy do tej pory, to taki „Flashbots Hello World” dla swapów na Uniswap V3. Żeby to miało ręce i nogi w realnych warunkach, trzeba to trochę dopieścić.

 

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("BRAK 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 (łamie wzorzec zachowań) ---
            if (Math.random() < 0.6) return;

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

            // --- THRESHOLD (próg opłacalności) ---
            if (quote.amountOut < minThreshold) return;

            // --- DETEKCJA ZMIAN ---
            if (lastQuote !== 0n) {
                const diff = (quote.amountOut * 1000n) / lastQuote;
                // ignoruj jeśli zmiana < 0.3%
                if (diff > 997n && diff < 1003n) return;
            }

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

            // --- RANDOM AMOUNT (łamie sygnaturę transakcji) ---
            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++; // WAŻNE
            if (res === FlashbotsBundleResolution.BundleIncluded) {
                console.log("WYKONANO W BLOKU:", target);
                process.exit(0);
            }

            lastQuote = quote.amountOut;
            lastExecutionBlock = block;
        } catch (e) {
            // tryb cichy
        }
    });
}

main();

 

3. Jak używać Anti-MEV Stealth Swap

1. Instalacja

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

2. Stwórz plik .env

ETH_RPC_URL=https://mainnet.infura.io/v3/TWÓJ_KLUCZ
SENDER_PRIVATE_KEY=TWÓJ_KLUCZ_PRYWATNY
FLASHBOTS_AUTH_KEY=DOWOLNY_NOWY_KLUCZ_PRYWATNY

3. Przygotowanie portfela

Niezbędne:

  • Musisz mieć Ethereum (ETH) na portfelu → na opłaty gas.
  • Oraz Wrapped Ether (WETH) → do samej wymiany.

Jeśli nie masz WETH:

  • Najpierw zamień ETH na WETH przez dowolną stronę (np. Uniswap).

4. Odpalenie

node app.js 0.1 200

Gdzie:

  • 0.1 → ile WETH chcesz wymienić.
  • 200 → minimalna akceptowalna ilość USDC na wyjściu.

5. Jak to działa pod maską?

Po starcie:

  • Sprawdza stan WETH.
  • Robi approve (tylko raz).
  • Zaczyna nasłuchiwać nowe bloki.
  • W każdym bloku:
    • Czasem celowo odpuszcza (losowość).
    • Sprawdza cenę przez Quoter.
    • Weryfikuje Twój próg opłacalności.
    • Sprawdza, czy cena się ruszyła (unika stagnacji).
    • Lekko modyfikuje kwotę transakcji (dla niepoznaki).
    • Robi symulację.
    • Wysyła przez Flashbots.

6. Kiedy dochodzi do transakcji?

  • Cena ≥ podanego progu.
  • Cena uległa zmianie.
  • Minął czas cooldownu.
  • Symulacja przeszła pomyślnie.

7. Wynik

Gdy zobaczysz w konsoli:

EXECUTED: 19483921

Oznacza to, że:

  • Wymiana siadła.
  • Skrypt zakończył pracę.

8. Kluczowe parametry
Próg (drugi argument)

node app.js 0.1 220

Wyższy próg =

  • Mniej transakcji.
  • Lepsza cena wykonania.

Kwota

node app.js 0.05 200

Mniejsza kwota =

  • Mniejszy wpływ na rynek (price impact).
  • Niższe ryzyko.

9. O czym trzeba pamiętać?

  • To nie gwarantuje zawsze najlepszej ceny na świecie.
  • Nie chroni przed absolutnie każdym rodzajem ataku.
  • Ale drastycznie zmniejsza szansę, że ktoś Cię „ogoli” (front-run).

10. Kiedy NIE używać?

  • Jeśli nie kumasz, jak działa slippage.
  • Gdy masz za mało środków na pokrycie kosztów.
  • Gdy sieć jest całkowicie zapchana.

Podsumowanie

  • Odpalasz jedną komendą.
  • Działa z automatu.
  • Robi „cichy” swap przez Flashbots.

Ten kod:

  • Nie wygra z profesjonalnymi botami MEV w bezpośrednim starciu.
  • Nie zabezpieczy Cię w każdym możliwym scenariuszu.
  • Nie daje magicznej przewagi (edge).

ALE:
👉 Przestajesz być łatwym łupem.

 

To narzędzie to dopiero wierzchołek góry lodowej. Można je rozbudować pod arbitraż, likwidacje albo bezpieczne przesuwanie grubych milionów płynności.

Zasada jest prosta: W „ciemnym lesie” blockchaina wygrywa nie ten, kto najgłośniej krzyczy, ale ten, kto potrafi skradać się bezszelestnie.

Materiał przygotowany dla Akademii EXMON. Eksperymentujcie w Mainnecie ostrożnie i zawsze pilnujcie ustawień slippage'u!

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....

Dodaj opinię

Twój adres e-mail nie zostanie opublikowany. Obowiązkowe pola są oznaczone*