Oke, di dua artikel sebelumnya kita sudah membuat "saluran komunikasi aman" dan menyusun logika pertukarannya. Di bagian terakhir ini, kita akan merampungkan alatnya. Kita bakal menerapkan siklus tunggu blok dan membungkus semuanya ke dalam antarmuka baris perintah (CLI) yang praktis.
1. Logika Pengiriman: Kenapa nggak bisa asal klik "Send"?
Jaringan Ethereum menghasilkan blok kira-kira setiap 12 detik. Bundle transaksi Anda hanya valid untuk nomor blok tertentu. Jika tidak masuk ke blok terdekat (misalnya karena tip gas terlalu rendah), Anda harus menyusun ulang bundle tersebut untuk blok berikutnya dengan nomor blok yang diperbarui.
Kita akan menggunakan perulangan (loop) yang mencoba "mendorong" Stealth Swap kita selama 10 blok ke depan.
2. Kode: Implementasi Pengiriman dan CLI
Mari tambahkan metode final sendBundle dan penanganan argumen baris perintah yang sederhana.
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(`Mulai pada blok: ${currentBlock}`);
// Mencoba mengirim bundle dalam 10 blok ke depan
for (let i = 0; i < 10; i++) {
const targetBlock = currentBlock + i;
// Menyusun ulang bundle untuk blok tertentu (termasuk simulasi)
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(`Gagal mengirim: ${bundleSubmission.error.message}`);
continue;
}
console.log(`Bundle terkirim. Menunggu blok ${targetBlock}...`);
const waitResponse = await bundleSubmission.wait();
if (waitResponse === FlashbotsBundleResolution.BundleIncluded) {
console.log(`MENANG! Transaksi masuk di blok ${targetBlock}`);
console.log(`Hash: https://etherscan.io/tx/${(await signedBundle)[0].hash}`); // Estimasi hash
return;
} else if (waitResponse === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
console.log(`Meleset. Blok ${targetBlock} lewat tanpa kita. Coba blok berikutnya...`);
} else if (waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
console.error("Error: Nonce terlalu tinggi. Cek transaksi yang masih pending.");
return;
}
}
}
// Antarmuka CLI sederhana
const amount = process.argv[2] || "0.01";
runStealthSwap(amount);Tentu saja, apa yang kita buat sejauh ini baru sebatas "Flashbots Hello World" untuk swap lewat Uniswap V3. Biar alat ini benar-benar layak pakai (production-ready), kita perlu memoles logikanya sedikit lagi.
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("SALDO WETH TIDAK CUKUP");
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 (merusak pola bot pemantau) ---
if (Math.random() < 0.6) return;
const quote = await quoter.quoteExactInputSingle.staticCall({
tokenIn: WETH,
tokenOut: USDC,
amountIn: baseAmount,
fee: 3000,
sqrtPriceLimitX96: 0
});
// --- AMBANG BATAS (Threshold) ---
if (quote.amountOut < minThreshold) return;
// --- DETEKSI PERUBAHAN ---
if (lastQuote !== 0n) {
const diff = (quote.amountOut * 1000n) / lastQuote;
// abaikan jika perubahan harga < 0.3%
if (diff > 997n && diff < 1003n) return;
}
// --- JEDA (Cool Down) ---
if (block - lastExecutionBlock < 2) return;
// --- JUMLAH ACAK (merusak tanda tangan transaksi) ---
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++; // PENTING
if (res === FlashbotsBundleResolution.BundleIncluded) {
console.log("EKSEKUSI BERHASIL PADA BLOK:", target);
process.exit(0);
}
lastQuote = quote.amountOut;
lastExecutionBlock = block;
} catch (e) {
// mode senyap untuk kegagalan
}
});
}
main();
3. Cara Menggunakan Anti-MEV Stealth Swap
1. Instalasi
npm install ethers @flashbots/ethers-provider-bundle dotenv2. Buat file .env
ETH_RPC_URL=https://mainnet.infura.io/v3/KEY_ANDA
SENDER_PRIVATE_KEY=PRIVATE_KEY_ANDA
FLASHBOTS_AUTH_KEY=ANY_NEW_PRIVATE_KEY3. Persiapan Dompet
Wajib ada:
- Saldo Ethereum (ETH) di dompet → untuk bayar biaya gas.
- Wrapped Ether (WETH) → untuk ditukar (swap).
Jika tidak punya WETH:
- Tukar dulu ETH jadi WETH lewat interface apa pun (misalnya Uniswap).
4. Menjalankan Skrip
node app.js 0.1 200Keterangan:
- 0.1 → berapa banyak WETH yang mau ditukar.
- 200 → jumlah minimum USDC yang bisa diterima.
5. Cara Kerjanya
Setelah dijalankan:
- Skrip cek saldo WETH.
- Melakukan approve (cukup sekali).
- Mulai memantau setiap blok baru.
- Di setiap blok:
- Kadang dilewati sengaja (random skip).
- Cek harga lewat Quoter.
- Cek apakah harga masuk ambang batas.
- Cek apakah harga sedang bergerak (bukan stagnan).
- Ubah sedikit jumlah transaksi secara acak.
- Jalankan simulasi.
- Kirim lewat Flashbots.
6. Kapan Transaksi Terjadi?
- Harga ≥ ambang batas yang ditentukan.
- Harga berubah (tidak diam di tempat).
- Sudah melewati masa jeda (cooldown).
- Simulasi dinyatakan sukses.
7. Hasilnya
Di konsol akan muncul:
EXECUTED: 19483921Artinya:
- Swap sukses dilakukan.
- Skrip selesai bekerja.
8. Parameter Penting
Ambang Batas (Argumen kedua)
node app.js 0.1 220Makin tinggi ambang batas =
- Makin sedikit transaksi yang terpicu.
- Harga eksekusi makin bagus.
Jumlah (Amount)
node app.js 0.05 200Makin kecil jumlah =
- Dampak ke harga pasar makin kecil.
- Risiko makin rendah.
9. Hal yang perlu dipahami
- Ini bukan jaminan harga terbaik setiap saat.
- Ini tidak melindungi dari semua jenis serangan.
- Tapi, ini drastis mengurangi kemungkinan Anda "dimangsa" oleh bot front-run.
10. Kapan JANGAN gunakan ini
- Kalau Anda nggak paham cara kerja slippage.
- Kalau saldo tidak cukup.
- Kalau jaringan sedang macet total.
Kesimpulan
- Jalankan cukup dengan 1 perintah.
- Bekerja secara otomatis.
- Melakukan swap "senyap" lewat Flashbots.
Ingat:
- Skrip ini nggak bakal mengalahkan bot MEV profesional dalam kompetisi kecepatan.
- Nggak menjamin aman di semua skenario.
- Bukan alat ajaib untuk cari cuan (edge).
TAPI:
👉 Anda berhenti jadi "makanan empuk".
Alat ini hanyalah puncak gunung es. Logikanya bisa dikembangkan untuk arbitrase, likuidasi, atau sekadar memindahkan likuiditas besar dengan aman.
Yang paling penting: Di "hutan gelap" blockchain, yang bertahan hidup bukanlah yang paling berisik, tapi yang paling ahli menyelinap tanpa suara.
Materi ini disusun untuk EXMON Academy. Bereksperimenlah di Mainnet dengan hati-hati dan selalu cek parameter slippage Anda!