Press ESC to close

Anti-MEV Stealth Swap: Final Showdown - Sending and CLI

Alright, so in the previous two articles, we set up our "secure communication channel" and scripted the swap logic. In this final part, we’re bringing it all together. We’ll implement a block-waiting loop and wrap everything into a clean Command Line Interface (CLI) tool.

1. Send Logic: Why you can't just hit "Send"

The Ethereum network pumps out blocks roughly every 12 seconds. Your bundle is only valid for one specific block number. If it doesn't make it into the next block (usually because your tip was too low), you have to rebuild it for the following block with an updated target number.

We’re going to use a loop that tries to "push" our Stealth Swap through over the course of the next 10 blocks.

 

2. Code: Implementation and CLI

Let's add the final sendBundle method and some basic CLI argument handling.

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(`Starting at block: ${currentBlock}`);

    // Try to land the bundle within the next 10 blocks
    for (let i = 0; i < 10; i++) {
        const targetBlock = currentBlock + i;
        
        // Rebuild the bundle for the specific block (includes 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(`Submission error: ${bundleSubmission.error.message}`);
            continue;
        }

        console.log(`Bundle sent. Waiting for block ${targetBlock}...`);
        const waitResponse = await bundleSubmission.wait();
        
        if (waitResponse === FlashbotsBundleResolution.BundleIncluded) {
            console.log(`WIN! Transaction landed in block ${targetBlock}`);
            console.log(`Hash: https://etherscan.io/tx/${(await signedBundle)[0].hash}`); // Estimated hash
            return;
        } else if (waitResponse === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
            console.log(`Missed it. Block ${targetBlock} passed without us. Trying the next one...`);
        } else if (waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
            console.error("Error: Nonce too high. Check your pending transactions.");
            return;
        }
    }
}

// Basic CLI entry point
const amount = process.argv[2] || "0.01";
runStealthSwap(amount);

Of course, what we've built so far is essentially a "Flashbots Hello World" for Uniswap V3 swaps. To make this a legit, production-ready tool, we need to sharpen the logic a bit.

 

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("INSUFFICIENT 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 (pattern breaking) ---
            if (Math.random() < 0.6) return;

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

            // --- THRESHOLD CHECK ---
            if (quote.amountOut < minThreshold) return;

            // --- CHANGE DETECTION ---
            if (lastQuote !== 0n) {
                const diff = (quote.amountOut * 1000n) / lastQuote;
                // ignore if price change is < 0.3%
                if (diff > 997n && diff < 1003n) return;
            }

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

            // --- RANDOM AMOUNT (signature breaking) ---
            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++; // CRITICAL
            if (res === FlashbotsBundleResolution.BundleIncluded) {
                console.log("EXECUTED AT BLOCK:", target);
                process.exit(0);
            }

            lastQuote = quote.amountOut;
            lastExecutionBlock = block;
        } catch (e) {
            // silent fail for production-like behavior
        }
    });
}

main();

 

3. How to use Anti-MEV Stealth Swap

1. Installation

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

2. Set up your .env

ETH_RPC_URL=https://mainnet.infura.io/v3/YOUR_KEY
SENDER_PRIVATE_KEY=YOUR_PRIVATE_KEY
FLASHBOTS_AUTH_KEY=ANY_NEW_PRIVATE_KEY

3. Prep your wallet

Must haves:

  • ETH in the wallet → to cover gas fees.
  • Wrapped Ether (WETH) → to perform the swap.

If you don't have WETH:

  • Wrap your ETH into WETH first via any UI (like Uniswap).

4. Execution

node app.js 0.1 200

Breaking it down:

  • 0.1 → Amount of WETH to swap.
  • 200 → Minimum USDC you're willing to accept.

5. How it works

Once you fire it up:

  • It checks your WETH balance.
  • Handles the one-time approval.
  • Starts monitoring blocks.
  • For every block:
    • Randomly skips some (to avoid being predictable).
    • Grabs the current price via Quoter.
    • Checks your minimum threshold.
    • Watches for price volatility.
    • Randomizes the trade size slightly.
    • Runs a simulation.
    • Sends it through Flashbots.

6. When does the trade actually happen?

  • Price is ≥ your specified threshold.
  • Price is moving (no stagnation).
  • The cooldown period has passed.
  • The simulation comes back clean.

7. The Result

When you see:

EXECUTED: 19483921

It means:

  • The swap is done.
  • The script has finished its job.

8. Key Parameters
Threshold (second argument)

node app.js 0.1 220

Higher threshold =

  • Fewer trades triggered.
  • Better execution price.

Amount

node app.js 0.05 200

Lower amount =

  • Less market impact.
  • Lower risk profile.

9. Reality Check

  • This doesn't guarantee you'll always get the best price.
  • It's not a silver bullet against every possible exploit.
  • It simply makes it way harder for others to front-run you.

10. When NOT to use this

  • If you don't understand how slippage works.
  • If you're low on funds.
  • If the network is absolutely slammed.

Summary

  • Run it with one command.
  • Fully automated.
  • Performs a "silent" swap via Flashbots.

Keep in mind:

  • This won't beat professional MEV bots at their own game.
  • It's not meant to cover every edge case.
  • It doesn't give you a massive "edge."

BUT:
👉 It stops you from being easy prey.

 

This tool is just the tip of the iceberg. You can scale this logic for arbitrage, liquidations, or moving large amounts of liquidity without tipping your hand.

Just remember: In the "dark forest" of blockchain, it’s not the loudest who survives, but the one who knows how to move in silence.

Content created for the EXMON Academy. Play around in Mainnet with caution and always double-check your slippage!

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

Leave a comment

Your email address will not be published. Required fields are marked *