Appuyez sur ESC pour fermer

Anti-MEV Stealth Swap : Créer et simuler un bundle

Dans la première partie, on a mis en place notre « tunnel de communication sécurisé ». Dans cette partie, on s'attaque au vif du sujet : la logique de swap. Notre mission est de construire une transaction Uniswap V3 qui soit non seulement privée, mais aussi garantie d'être exécutée sans accroc.

1. Préparation de la transaction de swap

Pour l'exemple, on va mettre en place un swap ETH vers USDC. Pour que la transaction soit valide au sein d'un bundle, on doit préparer l'objet de la transaction en amont, sans l'envoyer directement sur le réseau.

Ce qu'il nous faut sous la main :

  • L'adresse du Router Uniswap V3.
  • L'ABI (au moins le strict minimum) pour la fonction exactInputSingle.
  • Un calcul des frais de gaz incluant la priorité.

 

La règle d'or : Dans les bundles Flashbots, le gasPrice classique est remplacé par maxFeePerGas et maxPriorityFeePerGas. C'est précisément le priorityFee (le « pourboire » pour le validateur) qui va déterminer si votre bundle finit dans un bloc ou non.

 

 

2. Code : Assemblage et Simulation

On ajoute le fichier swap.ts à notre projet. Le gros morceau ici, c'est la méthode .simulate(). C'est la fonctionnalité phare de Flashbots : elle permet de tester l'exécution de la transaction sur l'état actuel de la blockchain sans claquer un seul centime en gaz réel.

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

// ABI minimal pour interagir avec le Router Uniswap V3
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. On prépare l'interface et les données de la transaction
    const iface = new ethers.Interface(ROUTER_ABI);
    const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes de marge
    
    const amountIn = ethers.parseEther("0.1"); // On swap 0.1 ETH
    const params = {
        tokenIn: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
        tokenOut: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
        fee: 3000, // 0.3%
        recipient: wallet.address,
        deadline: deadline,
        amountIn: amountIn,
        amountOutMinimum: 0, // En prod, calculez toujours le slippage !
        sqrtPriceLimitX96: 0
    };

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

    // 2. Structure de la transaction
    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"), // Le fameux « pot-de-vin » au validateur
        nonce: await wallet.getNonce()
    };

    // 3. Création du bundle signé
    const signedBundle = await flashbotsProvider.signBundle([
        {
            signer: wallet,
            transaction: transaction
        }
    ]);

    // 4. SIMULATION (Étape cruciale)
    console.log("Lancement de la simulation du bundle...");
    const simulation = await flashbotsProvider.simulate(signedBundle, nextBlockNumber);

    if ("error" in simulation) {
        console.error(`Échec de la simulation : ${simulation.error.message}`);
        return;
    }

    console.log("Simulation réussie !", JSON.stringify(simulation, null, 2));
    return signedBundle;
}

 

3. Le point technique : Pourquoi la simulation est indispensable ?

Sur le mempool « public », si votre transaction échoue (par exemple, limite de gaz trop courte ou prix qui bouge), elle est quand même incluse dans un bloc et vous payez les frais de gaz pour rien.

Dans l'univers Flashbots :

  • Si la simulation renvoie une erreur, vous n'envoyez simplement pas le bundle.
  • Si le bundle est envoyé mais que les conditions du bloc changent et rendent la transaction non rentable, le validateur l'ignorera purement et simplement.

En résumé : Vous ne payez le gaz que si votre Stealth Swap est effectivement exécuté.

4. Comment calculer le « pot-de-vin » (Priority Fee)

Les validateurs classent les bundles selon leur rentabilité. Le score de profitabilité d'un bundle (Gas Price Score) se calcule ainsi :

formule de calcul
 

Pour un swap classique, un maxPriorityFeePerGas de 1 ou 2 gwei suffit généralement. Mais en période de forte volatilité, la compétition pour une place dans le bloc grimpe en flèche, même sur les circuits privés.

 

Où en sommes-nous ?

On a maintenant un bundle signé et vérifié, prêt à être balancé. On sait avec certitude que le code Uniswap va rouler correctement et que le solde du wallet couvre bien tous les frais.

Dans la prochaine (et dernière) partie : on va coder la boucle d'attente de bloc, envoyer réellement le bundle et pondre une interface CLI sympa pour notre outil.

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

Partager votre avis

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués *