Naciśnij ESC, aby zamknąć

Anti-MEV Stealth Swap: Budowa i symulacja bundle'a

W pierwszej części postawiliśmy nasz "bezpieczny tunel komunikacyjny”. W tej części przechodzimy do pisania logiki swapu. Naszym zadaniem jest przygotowanie transakcji na Uniswap V3 tak, aby nie tylko była prywatna, ale i miała gwarancję wykonania.

1. Przygotowanie transakcji swapu

Dla przykładu zrealizujemy wymianę ETH na USDC. Aby transakcja była poprawna dla bundle'a, musimy przygotować jej obiekt wcześniej, bez wysyłania go bezpośrednio do sieci.

Będziemy potrzebować:

  • Adresu Routera Uniswap V3.
  • ABI (chociażby minimalnego) dla funkcji exactInputSingle.
  • Kalkulacji gazu z uwzględnieniem priorytetu.

 

Złota zasada: W paczkach Flashbots (bundles) standardowe gasPrice zastępuje się przez maxFeePerGas oraz maxPriorityFeePerGas. To właśnie priorityFee (czyli "napiwek" dla walidatora) decyduje o tym, czy Twój bundle w ogóle wskoczy do bloku.

 

 

2. Kod: Składanie i symulacja

Dodajmy do naszego projektu plik swap.ts. Główny nacisk kładziemy tutaj na metodę .simulate(). To absolutny "killer-feature" Flashbots, który pozwala sprawdzić, czy transakcja przejdzie na aktualnym stanie blockchaina, nie wydając przy tym ani grosza na realny gaz.

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

// Minimalne ABI do interakcji z Uniswap V3 Router
const ROUTER_ABI = [
    "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)"
];

const ROUTER_ADDRESS = "0xE592427A0AEce92De3Edee1F18E0157C05861564";

export async function createAndSimulateBundle(wallet: ethers.Wallet, flashbotsProvider: any, provider: ethers.Provider) {
    const block = await provider.getBlock("latest");
    const nextBlockNumber = block!.number + 1;

    // 1. Tworzymy interfejs i przygotowujemy dane transakcji
    const iface = new ethers.Interface(ROUTER_ABI);
    const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minut zapasu
    
    const amountIn = ethers.parseEther("0.1"); // Wymieniamy 0.1 ETH
    const params = {
        tokenIn: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
        tokenOut: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
        fee: 3000, // 0.3%
        recipient: wallet.address,
        deadline: deadline,
        amountIn: amountIn,
        amountOutMinimum: 0, // Na produkcji zawsze liczcie slippage!
        sqrtPriceLimitX96: 0
    };

    const data = iface.encodeFunctionData("exactInputSingle", [params]);

    // 2. Struktura transakcji
    const transaction = {
        to: ROUTER_ADDRESS,
        value: amountIn,
        data: data,
        chainId: 1,
        type: 2, // EIP-1559
        gasLimit: 250000,
        maxFeePerGas: ethers.parseUnits("50", "gwei"),
        maxPriorityFeePerGas: ethers.parseUnits("2", "gwei"), // "Łapówka" dla walidatora
        nonce: await wallet.getNonce()
    };

    // 3. Tworzymy podpisany bundle
    const signedBundle = await flashbotsProvider.signBundle([
        {
            signer: wallet,
            transaction: transaction
        }
    ]);

    // 4. SYMULACJA (Kluczowy etap)
    console.log("Odpalam symulację bundle'a...");
    const simulation = await flashbotsProvider.simulate(signedBundle, nextBlockNumber);

    if ("error" in simulation) {
        console.error(`Błąd symulacji: ${simulation.error.message}`);
        return;
    }

    console.log("Symulacja udana!", JSON.stringify(simulation, null, 2));
    return signedBundle;
}

 

3. Kwestie techniczne: Dlaczego symulacja to mus?

W "publicznym" Ethereum, jeśli Twoja transakcja zfailuje (np. braknie limitu gazu albo cena się zmieni), i tak trafi do bloku, a Ty zapłacisz za gaz.

W świecie Flashbots wygląda to inaczej:

  • Jeśli symulacja wypluje błąd – po prostu nie wysyłasz bundle'a.
  • Jeśli bundle zostanie wysłany, ale warunki w bloku się zmieniły i transakcja stała się nieopłacalna – walidator ją po prostu zignoruje.

Podsumowując: Płacisz za gaz tylko wtedy, gdy Twój Stealth Swap faktycznie się wykona.

4. Jak wyliczyć "łapówkę" (Priority Fee)

Walidatorzy wrzucają paczki do bloku w kolejności ich rentowności. Opłacalność bundle'a (Gas Price Score) liczona jest w ten sposób:

formula7
 

Przy zwykłym swapie maxPriorityFeePerGas na poziomie 1-2 gwei zazwyczaj styka. Ale przy dużej zmienności konkurencja o miejsce w bloku rośnie nawet na tych prywatnych torach.

 

Co mamy na ten moment?

Mamy gotowy, podpisany i sprawdzony bundle, który tylko czeka na wysyłkę. Wiemy na stówę, że kod Uniswapa zadziała poprawnie, a na portfelu starczy środków na pokrycie kosztów.

W kolejnej (ostatniej) części: napiszemy pętlę czekającą na blok, ogarniemy realną wysyłkę bundle'a i zrobimy wygodny interfejs CLI dla naszego narzędzia.

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*