Na primeira parte, a gente montou o nosso "tunnel de comunicação seguro". Nesta parte, vamos direto ao que interessa: a lógica do swap. Nosso objetivo é construir uma transação para a Uniswap V3 de forma que ela não seja apenas privada, mas que tenha execução garantida.
1. Preparando a transação de swap
Para o nosso exemplo, vamos configurar uma troca de ETH por USDC. Para que a transação seja válida dentro de um bundle, precisamos preparar o objeto dela com antecedência, sem transmiti-la diretamente para a rede.
O que vamos precisar ter em mãos:
- O endereço do Router da Uniswap V3.
- O ABI (pelo menos o mínimo) para a função
exactInputSingle. - O cálculo do gás levando em conta a prioridade.
Regra de ouro: Nos bundles do Flashbots, o
gasPricepadrão é substituído pelomaxFeePerGase pelomaxPriorityFeePerGas. É justamente o priorityFee (a "gorjeta" para o validador) que define se o seu bundle vai entrar no bloco ou não.
2. Código: Montagem e Simulação
Vamos adicionar ao nosso projeto o arquivo swap.ts. O grande destaque aqui é o método .simulate(). Essa é a "funcionalidade matadora" do Flashbots, que permite testar a execução da transação no estado atual da blockchain sem gastar um centavo de gás real.
import { ethers } from "ethers";
import { FlashbotsBundleRawTransaction } from "@flashbots/ethers-provider-bundle";
// ABI mínima para interagir com o Router da 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. Criamos a interface e preparamos os dados da transação
const iface = new ethers.Interface(ROUTER_ABI);
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // Janela de 20 minutos
const amountIn = ethers.parseEther("0.1"); // Vamos trocar 0.1 ETH
const params = {
tokenIn: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
tokenOut: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
fee: 3000, // Pool de 0.3%
recipient: wallet.address,
deadline: deadline,
amountIn: amountIn,
amountOutMinimum: 0, // Em produção, sempre calcule o slippage!
sqrtPriceLimitX96: 0
};
const data = iface.encodeFunctionData("exactInputSingle", [params]);
// 2. Estrutura da transação
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"), // A "propina" para o validador
nonce: await wallet.getNonce()
};
// 3. Criamos o bundle assinado
const signedBundle = await flashbotsProvider.signBundle([
{
signer: wallet,
transaction: transaction
}
]);
// 4. SIMULAÇÃO (Etapa crucial)
console.log("Iniciando simulação do bundle...");
const simulation = await flashbotsProvider.simulate(signedBundle, nextBlockNumber);
if ("error" in simulation) {
console.error(`Erro na simulação: ${simulation.error.message}`);
return;
}
console.log("Simulação bem-sucedida!", JSON.stringify(simulation, null, 2));
return signedBundle;
}
3. Detalhe técnico: Por que a simulação é obrigatória?
No mempool "público", se a sua transação falhar (por exemplo, o limite de gás for insuficiente ou o preço mudar), ela ainda assim entra no bloco e você paga as taxas de gás de qualquer jeito.
No mundo do Flashbots:
- Se a simulação der erro, você simplesmente não envia o bundle. Sem prejuízo.
- Se o bundle for enviado, mas as condições do bloco mudarem e a transação deixar de ser vantajosa, o validador simplesmente a ignora.
Resumo da ópera: Você só paga o gás quando o seu Stealth Swap for efetivamente executado.
4. Como calcular a "Propina" (Priority Fee)
Os validadores escolhem os bundles pela ordem de rentabilidade para eles. A lucratividade de um bundle (Gas Price Score) é calculada assim:

Para um swap comum, 1-2 gwei de maxPriorityFeePerGas costuma ser suficiente. Mas em momentos de alta volatilidade, a briga por um lugar no bloco aperta, mesmo nessas trilhas privadas.
O que temos até agora?
Já temos um bundle assinado e verificado, pronto para o combate. Sabemos com certeza que o código da Uniswap vai rodar redondo e que o saldo da carteira cobre todos os custos.
Na próxima (e última) parte: vamos escrever o loop de espera do bloco, fazer o envio real do bundle e criar uma interface CLI amigável para a nossa ferramenta.