في هذا الدرس الاحترافي، سنتعلم كيفية إعداد بيئة تطوير Web3 أساسية من خلال تثبيت محفظة MetaMask والاتصال بشبكات الاختبار (Testnets) المختلفة، ثم التفاعل معها باستخدام Ethers.js.
الخطوة الأولى: تثبيت محفظة MetaMask وإعدادها
MetaMask هي محفظة إيثيريوم غير احتجازية تعمل كإضافة للمتصفح، وتسمح لك بإدارة مفاتيحك الخاصة، إرسال واستقبال الأصول الرقمية، والتفاعل مع التطبيقات اللامركزية (dApps). إنها أداة لا غنى عنها لأي مطور Web3.
- توجه إلى الموقع الرسمي لـ MetaMask وقم بتنزيل الإضافة لمتصفحك (Chrome, Firefox, Edge, Brave).
- بعد التثبيت، انقر على أيقونة MetaMask في شريط أدوات المتصفح.
- انقر على "البدء" (Get Started) ثم "إنشاء محفظة" (Create a Wallet).
- وافق على الشروط والأحكام وأنشئ كلمة مرور قوية.
- الأهم: ستظهر لك "العبارة السرية للاسترداد" (Secret Recovery Phrase). اكتبها في مكان آمن ولا تشاركها مع أحد أبداً. هذه العبارة هي مفتاحك الوحيد للوصول إلى أموالك.
- قم بتأكيد العبارة السرية، وستكون محفظتك جاهزة.
ملاحظة تقنية: تعمل MetaMask كواجهة بين تطبيق الويب (dApp) وسلسلة الكتل (Blockchain)، حيث تقوم بحقن كائن window.ethereum في المتصفح، مما يسمح للتطبيقات بطلب توقيع المعاملات أو قراءة بيانات المحفظة.
الخطوة الثانية: الاتصال بشبكة Sepolia Testnet عبر MetaMask واستعراض الرصيد
شبكات الاختبار (Testnets) هي نسخ طبق الأصل من الشبكة الرئيسية (Mainnet) تسمح للمطورين باختبار عقودهم الذكية وتطبيقاتهم اللامركزية دون استخدام أموال حقيقية. سنتصل بشبكة Sepolia Testnet، وهي الشبكة الاختبارية الموصى بها حالياً.
- افتح MetaMask، انقر على قائمة الشبكات (عادةً "Ethereum Mainnet" في الأعلى).
- قم بتفعيل خيار "إظهار شبكات الاختبار" (Show test networks) إذا لم يكن مفعلاً.
- اختر "Sepolia" من قائمة الشبكات.
- للحصول على رصيد اختباري (Test ETH) على Sepolia، توجه إلى Sepolia Faucet. أدخل عنوان محفظتك (يمكنك نسخه من MetaMask بالنقر عليه) واتبع التعليمات للحصول على بعض ETH التجريبي.
الآن، لنكتب بعض الأكواد باستخدام Ethers.js للتفاعل مع محفظتك المتصلة بشبكة Sepolia.
// استيراد مكتبة Ethers.js (يجب تثبيتها مسبقاً: npm install ethers، أو استخدام CDN)
import { ethers } from "ethers";
// دالة غير متزامنة لتنفيذ المنطق
async function connectAndGetBalance() {
// التحقق مما إذا كانت MetaMask مثبتة ومتاحة
if (window.ethereum) {
// إنشاء موفر (Provider) باستخدام MetaMask (window.ethereum)
// هذا يسمح لنا بقراءة البيانات من البلوكتشين عبر MetaMask
const provider = new ethers.BrowserProvider(window.ethereum);
try {
// طلب من المستخدم ربط حسابه بـ MetaMask
// ستظهر نافذة منبثقة للمستخدم للموافقة
const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
const userAddress = accounts[0]; // الحصول على العنوان الأول المرتبط
console.log("تم الاتصال بنجاح!");
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">عنوان المحفظة المتصل: ${userAddress}</code>);
// الحصول على كائن Signer من الموفر
// Signer هو المسؤول عن توقيع المعاملات باستخدام المفتاح الخاص للمستخدم
const signer = await provider.getSigner();
// الحصول على رصيد العنوان المتصل بوحدة Wei
const balanceWei = await provider.getBalance(userAddress);
// تحويل الرصيد من Wei إلى Ether لسهولة القراءة
const balanceEther = ethers.formatEther(balanceWei);
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">رصيد المحفظة على شبكة Sepolia: ${balanceEther} ETH</code>);
// يمكنك أيضاً الحصول على معلومات الشبكة الحالية
const network = await provider.getNetwork();
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الشبكة المتصلة: ${network.name} (Chain ID: ${network.chainId})</code>);
} catch (error) {
console.error("حدث خطأ أثناء الاتصال أو جلب الرصيد:", error);
alert("فشل الاتصال بـ MetaMask أو رفض المستخدم. يرجى التأكد من تشغيل MetaMask.");
}
} else {
console.error("MetaMask غير مثبتة. يرجى تثبيتها للمتابعة.");
alert("MetaMask غير متوفرة. يرجى تثبيت إضافة MetaMask لمتصفحك.");
}
}
// استدعاء الدالة عند تحميل الصفحة أو عند وقوع حدث معين
connectAndGetBalance();
الخطوة الثالثة: إرسال معاملة اختبارية بسيطة
بعد الاتصال وجلب الرصيد، حان الوقت لإرسال معاملة اختبارية. هذا يوضح كيف يمكن لتطبيقك التفاعل مع محفظة المستخدم لتوقيع وإرسال المعاملات إلى شبكة الاختبار.
ملاحظة تقنية: عند إرسال معاملة تتطلب توقيعاً، ستقوم MetaMask بفتح نافذة منبثقة تطلب من المستخدم الموافقة على المعاملة قبل إرسالها إلى البلوكتشين. هذا يضمن أن المستخدم يتحكم بشكل كامل في أمواله.
// استكمالاً للكود السابق، داخل دالة connectAndGetBalance بعد جلب الرصيد
// ... (الكود السابق حتى السطر: console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الشبكة المتصلة: ${network.name} (Chain ID: ${network.chainId})</code>);)
// عنوان مستلم افتراضي لإرسال ETH اختباري إليه
// (يمكنك استبداله بأي عنوان Sepolia آخر)
const recipientAddress = "0xYourRecipientAddressHere"; // <--- استبدل هذا بعنوان مستلم حقيقي على Sepolia
// المبلغ الذي سيتم إرساله (مثلاً 0.001 ETH)
const amountToSend = ethers.parseEther("0.001"); // تحويل من Ether إلى Wei
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">محاولة إرسال ${ethers.formatEther(amountToSend)} ETH إلى ${recipientAddress}...</code>);
// إنشاء كائن المعاملة
const transaction = {
to: recipientAddress,
value: amountToSend,
// يمكن إضافة gasPrice و gasLimit هنا، ولكن Ethers.js و MetaMask
// عادةً ما يقومان بتقديرها تلقائياً بشكل جيد.
};
// إرسال المعاملة باستخدام Signer
// ستفتح MetaMask نافذة لتأكيد المعاملة
const txResponse = await signer.sendTransaction(transaction);
console.log("تم إرسال المعاملة بنجاح!");
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Hash المعاملة: ${txResponse.hash}</code>);
console.log("انتظر حتى يتم تأكيد المعاملة على البلوكتشين...");
// انتظار تأكيد المعاملة
const receipt = await txResponse.wait();
console.log("تم تأكيد المعاملة!");
console.log("تفاصيل الإيصال:", receipt);
} catch (error) {
console.error("حدث خطأ أثناء المعاملة:", error);
// يمكن التحقق من نوع الخطأ لتحديد ما إذا كان المستخدم قد رفض المعاملة
if (error.code === 4001) { // 4001 هو رمز خطأ المستخدم الذي يرفض المعاملة في MetaMask
alert("المستخدم رفض المعاملة.");
} else {
alert("فشل إرسال المعاملة. يرجى مراجعة وحدة التحكم.");
}
}
} else {
// ... (الكود السابق لعدم وجود MetaMask)
}
}
// استدعاء الدالة عند تحميل الصفحة أو عند وقوع حدث معين
// connectAndGetBalance(); // تم استدعاؤها بالفعل في الكود الكامل
الكود النهائي الكامل
إليك السكربت الكامل الذي يجمع الخطوات السابقة. يمكنك تضمينه في ملف HTML بسيط أو مشروع JavaScript.
<!DOCTYPE html>
<html lang="ar">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تفاعل مع Sepolia Testnet</title>
<!-- استيراد Ethers.js من CDN لسهولة التجربة. في مشروع حقيقي، قم بتثبيتها عبر npm -->
<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.esm.min.js"></script>
</head>
<body>
<h1>إعداد بيئة التطوير: تفاعل مع MetaMask و Sepolia Testnet</h1>
<p>افتح وحدة تحكم المطور (Developer Console) في متصفحك (F12) لمشاهدة المخرجات.</p>
<button id="connectButton">الاتصال بـ MetaMask وجلب الرصيد</button>
<button id="sendTxButton" disabled>إرسال 0.001 ETH</button>
<p>عنوان المستلم (للمعاملة): <input type="text" id="recipientAddressInput" value="0xYourRecipientAddressHere" size="42"></p>
<div id="output"></div>
<script type="module">
import { ethers } from "https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.esm.min.js"; // استيراد Ethers.js
let provider;
let signer;
let userAddress;
const connectButton = document.getElementById('connectButton');
const sendTxButton = document.getElementById('sendTxButton');
const recipientAddressInput = document.getElementById('recipientAddressInput');
const outputDiv = document.getElementById('output');
function logToOutput(message, isError = false) {
const p = document.createElement('p');
p.textContent = message;
if (isError) {
p.style.color = 'red';
}
outputDiv.appendChild(p);
console.log(message);
}
async function connectWallet() {
if (window.ethereum) {
provider = new ethers.BrowserProvider(window.ethereum);
try {
const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
userAddress = accounts[0];
logToOutput("تم الاتصال بنجاح!");
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">عنوان المحفظة المتصل: ${userAddress}</code>);
signer = await provider.getSigner();
const balanceWei = await provider.getBalance(userAddress);
const balanceEther = ethers.formatEther(balanceWei);
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">رصيد المحفظة على شبكة Sepolia: ${balanceEther} ETH</code>);
const network = await provider.getNetwork();
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الشبكة المتصلة: ${network.name} (Chain ID: ${network.chainId})</code>);
sendTxButton.disabled = false; // تفعيل زر الإرسال بعد الاتصال
connectButton.disabled = true; // تعطيل زر الاتصال بعد الاتصال
recipientAddressInput.value = "0xYourRecipientAddressHere"; // تعيين قيمة افتراضية أو عنوان آخر
} catch (error) {
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">حدث خطأ أثناء الاتصال أو جلب الرصيد: ${error.message}</code>, true);
alert("فشل الاتصال بـ MetaMask أو رفض المستخدم. يرجى التأكد من تشغيل MetaMask.");
}
} else {
logToOutput("MetaMask غير مثبتة. يرجى تثبيتها للمتابعة.", true);
alert("MetaMask غير متوفرة. يرجى تثبيت إضافة MetaMask لمتصفحك.");
}
}
async function sendTestTransaction() {
if (!signer || !userAddress) {
alert("الرجاء الاتصال بـ MetaMask أولاً.");
return;
}
const recipientAddress = recipientAddressInput.value;
if (!ethers.isAddress(recipientAddress) || recipientAddress === "0xYourRecipientAddressHere") {
alert("الرجاء إدخال عنوان مستلم صالح على شبكة Sepolia.");
return;
}
const amountToSend = ethers.parseEther("0.001");
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">محاولة إرسال ${ethers.formatEther(amountToSend)} ETH إلى ${recipientAddress}...</code>);
const transaction = {
to: recipientAddress,
value: amountToSend,
};
try {
const txResponse = await signer.sendTransaction(transaction);
logToOutput("تم إرسال المعاملة بنجاح!");
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Hash المعاملة: <a href="https://sepolia.etherscan.io/tx/${txResponse.hash}" target="_blank">${txResponse.hash}</a></code>);
logToOutput("انتظر حتى يتم تأكيد المعاملة على البلوكتشين...");
const receipt = await txResponse.wait();
logToOutput("تم تأكيد المعاملة!");
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">تفاصيل الإيصال: Block Number ${receipt.blockNumber}, Gas Used ${receipt.gasUsed}</code>);
// تحديث الرصيد بعد المعاملة
const newBalanceWei = await provider.getBalance(userAddress);
const newBalanceEther = ethers.formatEther(newBalanceWei);
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الرصيد الجديد: ${newBalanceEther} ETH</code>);
} catch (error) {
if (error.code === 4001) {
logToOutput("المستخدم رفض المعاملة.", true);
} else {
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">حدث خطأ أثناء إرسال المعاملة: ${error.message}</code>, true);
}
alert("فشل إرسال المعاملة. يرجى مراجعة وحدة التحكم.");
}
}
connectButton.addEventListener('click', connectWallet);
sendTxButton.addEventListener('click', sendTestTransaction);
// إضافة مستمع لحدث تغيير الحساب في MetaMask
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length > 0) {
userAddress = accounts[0];
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">تم تغيير الحساب إلى: ${userAddress}</code>);
// إعادة جلب الرصيد وتحديث الواجهة
connectWallet();
} else {
logToOutput("تم قطع الاتصال بـ MetaMask.", true);
userAddress = null;
signer = null;
sendTxButton.disabled = true;
connectButton.disabled = false;
}
});
// إضافة مستمع لحدث تغيير الشبكة في MetaMask
window.ethereum.on('chainChanged', (chainId) => {
logToOutput(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">تم تغيير الشبكة إلى Chain ID: ${parseInt(chainId, 16)}</code>);
// إعادة الاتصال لتحديث Provider و Signer
connectWallet();
});
</script>
</body>
</html>
النتيجة المتوقعة
عند فتح ملف HTML في المتصفح وتثبيت MetaMask، ستظهر لك الأزرار. عند النقر على "الاتصال بـ MetaMask وجلب الرصيد":
- ستنبثق نافذة MetaMask تطلب منك الموافقة على ربط حسابك بالموقع.
- بعد الموافقة، ستظهر في وحدة تحكم المطور (Developer Console) وعلى الشاشة المخرجات التالية:
تم الاتصال بنجاح!عنوان المحفظة المتصل: 0x... (عنوان محفظتك)رصيد المحفظة على شبكة Sepolia: X.XXX ETHالشبكة المتصلة: sepolia (Chain ID: 11155111)
- سيصبح زر "إرسال 0.001 ETH" مفعلاً. عند النقر عليه (بعد إدخال عنوان مستلم صحيح):
- ستنبثق نافذة MetaMask أخرى تطلب منك تأكيد المعاملة.
- بعد التأكيد، ستظهر في وحدة التحكم:
تم إرسال المعاملة بنجاح!Hash المعاملة: 0x... (رابط لمعاملتك على Sepolia Etherscan)انتظر حتى يتم تأكيد المعاملة على البلوكتشين...تم تأكيد المعاملة!تفاصيل الإيصال: Block Number ..., Gas Used ...الرصيد الجديد: X.XXX ETH (بعد خصم قيمة المعاملة)
هذا يؤكد أن بيئة التطوير الخاصة بك جاهزة للتفاعل مع شبكات الاختبار!