Pois bem, nos dois artigos anteriores, estabelecemos nosso "canal de comunicação seguro" e escrevemos a lógica do swap. Nesta última parte, finalizamos a ferramenta. Implementaremos o loop de espera pelos blocos e empacotaremos tudo em uma Interface de Linha de Comando (CLI) prática.
1. Lógica de Envio: Por que não clicar apenas em "Enviar"?
A rede Ethereum gera blocos aproximadamente a cada 12 segundos. Seu bundle (pacote de transações) é válido apenas para um número de bloco específico. Se ele não entrar no próximo bloco (por exemplo, devido a uma gorjeta de gás muito baixa), você precisará reconstruí-lo para o próximo bloco com o número atualizado.
Usaremos um loop que tentará "empurrar" nosso Stealth Swap pelos próximos 10 blocos.
2. Código: Implementação do Envio e CLI
Vamos adicionar o método final sendBundle e um tratamento simples de argumentos via terminal.
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(`Iniciando no bloco: ${currentBlock}`);
// Tentamos enviar o bundle durante os próximos 10 blocos
for (let i = 0; i < 10; i++) {
const targetBlock = currentBlock + i;
// Montamos o bundle novamente para o bloco específico (incluindo simulação)
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(`Erro no envio: ${bundleSubmission.error.message}`);
continue;
}
console.log(`Bundle enviado. Aguardando bloco ${targetBlock}...`);
const waitResponse = await bundleSubmission.wait();
if (waitResponse === FlashbotsBundleResolution.BundleIncluded) {
console.log(`VITÓRIA! Transação incluída no bloco ${targetBlock}`);
console.log(`Hash: https://etherscan.io/tx/${(await signedBundle)[0].hash}`); // Hash aproximado
return;
} else if (waitResponse === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
console.log(`Falha. O bloco ${targetBlock} passou sem nós. Tentando o próximo...`);
} else if (waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
console.error("Erro: Nonce muito alto. Verifique transações pendentes.");
return;
}
}
}
// Interface CLI básica
const amount = process.argv[2] || "0.01";
runStealthSwap(amount);Obviamente, o que fizemos até agora é apenas um "Olá Mundo" do Flashbots para swaps no Uniswap V3. Para que isso funcione de verdade em condições reais, precisamos refinar um pouco a lógica.
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);
// --- PRÉ-VERIFICAÇÃO ---
const balance = await weth.balanceOf(wallet.address);
if (balance < baseAmount) throw new Error("WETH INSUFICIENTE");
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 {
// --- PULO ALEATÓRIO (quebra padrões de comportamento) ---
if (Math.random() < 0.6) return;
const quote = await quoter.quoteExactInputSingle.staticCall({
tokenIn: WETH,
tokenOut: USDC,
amountIn: baseAmount,
fee: 3000,
sqrtPriceLimitX96: 0
});
// --- THRESHOLD (limite de lucratividade) ---
if (quote.amountOut < minThreshold) return;
// --- DETECÇÃO DE MUDANÇA ---
if (lastQuote !== 0n) {
const diff = (quote.amountOut * 1000n) / lastQuote;
// ignorar se a mudança for < 0.3%
if (diff > 997n && diff < 1003n) return;
}
// --- COOL DOWN ---
if (block - lastExecutionBlock < 2) return;
// --- QUANTIA ALEATÓRIA (quebra a assinatura da transação) ---
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++; // IMPORTANTE
if (res === FlashbotsBundleResolution.BundleIncluded) {
console.log("EXECUTADO NO BLOCO:", target);
process.exit(0);
}
lastQuote = quote.amountOut;
lastExecutionBlock = block;
} catch (e) {
// modo silencioso
}
});
}
main();
3. Como usar o Anti-MEV Stealth Swap
1. Instalação
npm install ethers @flashbots/ethers-provider-bundle dotenv2. Crie o arquivo .env
ETH_RPC_URL=https://mainnet.infura.io/v3/SUA_CHAVE
SENDER_PRIVATE_KEY=SUA_CHAVE_PRIVADA
FLASHBOTS_AUTH_KEY=QUALQUER_NOVA_CHAVE_PRIVADA3. Preparação da Carteira
Indispensável:
- Você deve ter Ethereum (ETH) na carteira → para taxas de gás.
- E Wrapped Ether (WETH) → para a troca em si.
Se você não tiver WETH:
- Primeiro converta ETH para WETH através de qualquer site (ex: Uniswap).
4. Execução
node app.js 0.1 200Onde:
- 0.1 → quanto WETH você deseja trocar.
- 200 → quantidade mínima aceitável de USDC na saída.
5. Como funciona por baixo do capô?
Após o início:
- Verifica o saldo de WETH.
- Faz o approve (apenas uma vez).
- Começa a ouvir novos blocos.
- Em cada bloco:
- Às vezes pula deliberadamente (aleatoriedade).
- Verifica o preço através do Quoter.
- Verifica seu limite de lucratividade.
- Verifica se o preço mudou (evita estagnação).
- Modifica levemente a quantia da transação (para disfarce).
- Realiza uma simulação.
- Envia via Flashbots.
6. Quando ocorre a transação?
- Preço ≥ limite especificado.
- O preço mudou.
- O tempo de cooldown passou.
- A simulação foi bem-sucedida.
7. Resultado
Quando vir no console:
EXECUTED: 19483921Isso significa que:
- O swap foi concluído.
- O script encerrou o trabalho.
8. Parâmetros Chave
Limite (segundo argumento)
node app.js 0.1 220Limite mais alto =
- Menos transações.
- Melhor preço de execução.
Quantia
node app.js 0.05 200Quantia menor =
- Menor impacto no mercado (price impact).
- Menor risco.
9. O que é preciso lembrar?
- Isso nem sempre garante o melhor preço absoluto do mundo.
- Não protege contra absolutamente todos os tipos de ataque.
- Mas reduz drasticamente a chance de alguém te dar um "front-run".
10. Quando NÃO usar?
- Se você não entende como o slippage funciona.
- Quando você tem poucos fundos para cobrir os custos de gás.
- Quando a rede está completamente congestionada.
Resumo
- Inicia com um comando.
- Funciona automaticamente.
- Faz um swap "silencioso" via Flashbots.
Este código:
- Não vencerá robôs MEV profissionais em um duelo direto.
- Não te protegerá em todos os cenários possíveis.
- Não oferece uma vantagem mágica (edge).
MAS:
👉 Você deixa de ser uma presa fácil.
Esta ferramenta é apenas a ponta do iceberg. Ela pode ser expandida para arbitragem, liquidações ou movimentação segura de grandes volumes de liquidez.
O princípio é simples: Na "floresta escura" do blockchain, vence não quem grita mais alto, mas quem sabe se esgueirar silenciosamente.
Material preparado para a EXMON Academy. Experimente na Mainnet com cautela e sempre monitore suas configurações de slippage!