في الجزء الأول، قمنا ببناء «قناة اتصال آمنة». في هذا الجزء، ننتقل إلى كتابة منطق المقايضة. مهمتنا هي إنشاء معاملة لمنصة Uniswap V3 بحيث لا تقتصر فقط على كونها خاصة، بل نضمن تنفيذها بنجاح.
1. تحضير معاملة المقايضة
كمثال، سنقوم بتنفيذ مقايضة ETH مقابل USDC. ولكي تكون المعاملة صالحة للحزمة (Bundle)، نحتاج إلى تجهيز كائن المعاملة مسبقاً دون إرساله مباشرة إلى الشبكة.
الأشياء التي سنحتاجها:
- عنوان راوتر (Router) الخاص بـ Uniswap V3.
- واجهة ABI (على الأقل الحد الأدنى) لوظيفة
exactInputSingle. - حساب الغاز (Gas) مع مراعاة الأولوية (Priority).
قاعدة ذهبية: في حزم Flashbots، يتم استبدال
gasPriceالتقليدي بـmaxFeePerGasوmaxPriorityFeePerGas. في الحقيقة، الـ priorityFee (أو "البقشيش" الذي يُدفع للمدقق) هو ما يحدد ما إذا كانت حزمتك ستدخل في البلوك أم لا.
2. الكود: التجميع والمحاكاة
سنضيف ملف swap.ts إلى مشروعنا. التركيز الأساسي هنا هو على وظيفة .simulate(). هذه هي "الميزة القاتلة" في Flashbots، فهي تتيح لك التحقق من تنفيذ المعاملة بناءً على حالة البلوكشين الحالية دون أن تصرف فلساً واحداً على الغاز الحقيقي.
import { ethers } from "ethers";
import { FlashbotsBundleRawTransaction } from "@flashbots/ethers-provider-bundle";
// واجهة ABI مصغرة للتفاعل مع راوتر 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. تجهيز الواجهة وبيانات المعاملة
const iface = new ethers.Interface(ROUTER_ABI);
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // مهلة 20 دقيقة
const amountIn = ethers.parseEther("0.1"); // سنقوم بمقايضة 0.1 ETH
const params = {
tokenIn: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
tokenOut: "0xA0b86991c6218b36c1d19D4a2e9Eb0ce3606eB48", // USDC
fee: 3000, // عمولة 0.3%
recipient: wallet.address,
deadline: deadline,
amountIn: amountIn,
amountOutMinimum: 0, // في المشاريع الحقيقية، احسب دائماً نسبة الانزلاق (slippage)!
sqrtPriceLimitX96: 0
};
const data = iface.encodeFunctionData("exactInputSingle", [params]);
// 2. هيكل المعاملة
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"), // "الرشوة" أو البقشيش للمدقق
nonce: await wallet.getNonce()
};
// 3. إنشاء الحزمة الموقعة
const signedBundle = await flashbotsProvider.signBundle([
{
signer: wallet,
transaction: transaction
}
]);
// 4. المحاكاة (مرحلة حاسمة جداً)
console.log("جاري تشغيل محاكاة الحزمة...");
const simulation = await flashbotsProvider.simulate(signedBundle, nextBlockNumber);
if ("error" in simulation) {
console.error(`فشلت المحاكاة: ${simulation.error.message}`);
return;
}
console.log("المحاكاة تمت بنجاح!", JSON.stringify(simulation, null, 2));
return signedBundle;
}
3. الجانب التقني: لماذا المحاكاة ضرورية؟
في "الميمبول" العام (Public Mempool)، إذا فشلت معاملتك (بسبب تجاوز حد الغاز أو تغير السعر مثلاً)، فستظل تدرج في البلوك وستدفع رسوم الغاز هباءً.
لكن في عالم Flashbots الأمر مختلف:
- إذا أظهرت المحاكاة خطأً، فأنت ببساطة لا ترسل الحزمة. ولن تخسر شيئاً.
- إذا تم إرسال الحزمة ولكن تغيرت الظروف في البلوك وأصبحت المعاملة غير مربحة، فإن المدقق سيتجاهلها تماماً.
الخلاصة: أنت تدفع مقابل الغاز فقط عندما يتم تنفيذ عملية الـ Stealth Swap الخاصة بك بنجاح.
4. كيف تحسب "الرشوة" (Priority Fee)
المدققون يختارون الحزم بناءً على مدى ربحيتها لهم. يتم حساب "درجة سعر الغاز" (Gas Price Score) للحزمة كالتالي:

إذا كنت تقوم بمقايضة عادية، فعادة ما يكون وضع maxPriorityFeePerGas بقيمة 1-2 gwei كافياً. لكن في أوقات التذبذب العالي، تزداد المنافسة على مكان في البلوك حتى في المسارات الخاصة.
أين وصلنا الآن؟
لدينا الآن حزمة موقعة ومفحوصة وجاهزة للإطلاق. نحن متأكدون تماماً من أن كود Uniswap سيعمل بشكل صحيح، وأن رصيد المحفظة يغطي جميع التكاليف.
في الجزء التالي والأخير: سنكتب حلقة الانتظار للبلوك، وسنقوم بالإرسال الفعلي للحزمة، وسنصمم واجهة CLI سهلة الاستخدام لأداتنا.