Нажмите ESC, чтобы закрыть

Анти-MEV Стелс Своп: Финальная часть - Отправка и CLI

Итак, в предыдущих двух статьях мы создали «защищенный канал связи» и написали логику обмена. В этой части мы завершаем создание инструмента. Мы реализуем цикл ожидания блока и обернем всё в удобный интерфейс командной строки (CLI).

1. Логика отправки: Почему нельзя просто нажать «Send»?

Сеть Ethereum генерирует блоки примерно каждые 12 секунд. Ваш бандл валиден только для конкретного номера блока. Если он не попал в ближайший блок (например, из-за низкой комиссии), его нужно пересобрать для следующего с обновленным номером.

Мы будем использовать цикл, который пытается «протолкнуть» наш Stealth Swap в течение следующих 10 блоков.

 

2. Код: Реализация отправки и CLI

Добавим финальный метод sendBundle и простую обработку аргументов командной строки.

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(`Старт на блоке: ${currentBlock}`);
    // Пытаемся отправить бандл в ближайшие 10 блоков
    for (let i = 0; i < 10; i++) {
        const targetBlock = currentBlock + i;
        
        // Пересобираем бандл для конкретного блока (включая симуляцию)
        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(`Ошибка отправки: ${bundleSubmission.error.message}`);
            continue;
        }
        console.log(`Бандл отправлен. Ожидаем блок ${targetBlock}...`);
        const waitResponse = await bundleSubmission.wait();
        
        if (waitResponse === FlashbotsBundleResolution.BundleIncluded) {
            console.log(`ПОБЕДА! Транзакция включена в блок ${targetBlock}`);
            console.log(`Хеш: https://etherscan.io/tx/${(await signedBundle)[0].hash}`); // Примерный расчет хеша
            return;
        } else if (waitResponse === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
            console.log(`Промах. Блок ${targetBlock} прошел без нас. Пробуем следующий...`);
        } else if (waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
            console.error("Ошибка: Нонс слишком высокий. Проверьте незавершенные транзакции.");
            return;
        }
    }
}
// Простейший CLI-интерфейс
const amount = process.argv[2] || "0.01";
runStealthSwap(amount);

Конечно, все что мы сделали - это не финальная версия для продакшн, это Flashbots Hello World для свапа через Uniswap V3. Давайте немного его доработаем, чтобы получился рабочий инструмент.

 

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("NO 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 (ломает паттерн) ---
            if (Math.random() < 0.6) return;
            const quote = await quoter.quoteExactInputSingle.staticCall({
                tokenIn: WETH,
                tokenOut: USDC,
                amountIn: baseAmount,
                fee: 3000,
                sqrtPriceLimitX96: 0
            });
            // --- THRESHOLD ---
            if (quote.amountOut < minThreshold) return;
            // --- CHANGE DETECTION ---
            if (lastQuote !== 0n) {
                const diff = (quote.amountOut * 1000n) / lastQuote;
                // игнор если изменение < 0.3%
                if (diff > 997n && diff < 1003n) return;
            }
            // --- COOL DOWN ---
            if (block - lastExecutionBlock < 2) return;
            // --- RANDOM AMOUNT (ломает сигнатуру) ---
            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++; // ВАЖНО
            if (res === FlashbotsBundleResolution.BundleIncluded) {
                console.log("EXECUTED:", target);
                process.exit(0);
            }
            lastQuote = quote.amountOut;
            lastExecutionBlock = block;
        } catch (e) {
            // тихий режим
        }
    });
}
main();

 

3. Как пользоваться Anti-MEV Stealth Swap

1. Установка

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

2. Создайте .env

ETH_RPC_URL=https://mainnet.infura.io/v3/ВАШ_KEY
SENDER_PRIVATE_KEY=ВАШ_PRIVATE_KEY
FLASHBOTS_AUTH_KEY=ЛЮБОЙ_НОВЫЙ_PRIVATE_KEY

3. Подготовка кошелька

Обязательно:

  • На кошельке должен быть Ethereum (ETH) → для газа
  • И Wrapped Ether (WETH) → для обмена

Если WETH нет:

  • сначала оберни ETH → WETH через любой интерфейс (например Uniswap)

4. Запуск

node app.js 0.1 200

Где:

  • 0.1 → сколько WETH менять
  • 200 → минимально допустимый выход в USDC

5. Как это работает

После запуска:

  • Проверяется баланс WETH
  • Делается approve (1 раз)
  • Скрипт начинает слушать блоки
  • На каждом блоке:
    • иногда пропускает (рандом)
    • берет цену через Quoter
    • проверяет порог
    • проверяет изменение цены
    • случайно меняет размер сделки
    • симулирует
    • отправляет через Flashbots

6. Когда происходит сделка Сделка выполняется только если:

  • цена ≥ указанного порога
  • цена изменилась (нет стагнации)
  • прошел cooldown
  • симуляция успешна

7. Результат

В консоли:

EXECUTED: 19483921

означает:

  • обмен выполнен
  • скрипт завершился

8. Важные параметры
Порог (второй аргумент)

node app.js 0.1 220

выше порог =

  • меньше сделок
  • лучше цена
     

Сумма

node app.js 0.05 200

меньше сумма =

  • меньше влияние на рынок
  • ниже риск

9. Что важно понимать

  • Это не гарантирует лучшую цену
  • Это не защищает от всех атак
  • Это снижает вероятность быть обыгранным

10. Когда НЕ использовать

  • если не понимаешь slippage
  • если нет достаточного баланса
  • если сеть перегружена

Итог

  • Запуск — 1 команда
  • Работает автоматически
  • Делает “тихий” свап через Flashbots
     

Этот код:

  • не победит профессиональные MEV боты
  • не защитит от всех сценариев
  • не даёт edge

НО:
👉 перестаёт быть легкой добычей

 

Этот инструмент — лишь верхушка айсберга. Его можно масштабировать для арбитража, ликвидаций или просто для безопасного перемещения крупных объемов ликвидности.

Главное помнить: в «темном лесу» блокчейна выживает не тот, кто громче всех кричит, а тот, кто умеет ходить бесшумно.

Материал подготовлен для Академии EXMON. Экспериментируйте в Mainnet осторожно и всегда проверяйте параметры слиппейджа!

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

Поделитесь своим мнением

Ваш e-mail не будет опубликован. Обязательные поля отмечены *