هندسة الويب اللامركزي (Web3.js & Ethers.js): كيف نربط الواجهات بالعقود الذكية؟

دقائق القراءة: 6

هندسة الويب اللامركزي (Web3.js & Ethers.js): كيف نربط الواجهات بالعقود الذكية؟

بعد أن يفهم المطور أساس Blockchain، ويجهز بيئته عبر إعداد بيئة التطوير: تثبيت محفظة MetaMask والاتصال بشبكات الاختبار، تظهر المرحلة الأهم عملياً: كيف تتحدث الواجهة الأمامية مع العقد الذكي بشكل آمن وموثوق؟ هنا يأتي دور مكتبتين محوريتين هما Web3.js وEthers.js.

وظيفة هاتين المكتبتين ليست “تشغيل العقد” داخل المتصفح، بل بناء طبقة اتصال بين واجهة المستخدم وشبكة Ethereum أو أي شبكة متوافقة مع EVM. من خلالها تستطيع قراءة البيانات، إرسال المعاملات، الاستماع إلى الأحداث Events، وإدارة تفاعل المستخدم مع محفظته الرقمية.

ما الفرق بين Web3.js وEthers.js؟

Web3.js هي مكتبة أقدم تاريخياً وانتشرت كثيراً في الجيل الأول من تطبيقات DApps. أما Ethers.js فتميزت ببنية أخف، وواجهة برمجية أكثر أناقة، واعتماد واسع داخل أدوات حديثة مثل Hardhat.

على المستوى المعماري، المكتبتان تنفذان نفس الفكرة الأساسية: الاتصال بمزود شبكة Provider، ثم إنشاء كائن يمثل العقد اعتماداً على ABI وعنوانه. بعد ذلك يمكن استدعاء الدوال read مجاناً تقريباً، أو إرسال دوال تغير الحالة عبر توقيع المستخدم.

متى تختار كل مكتبة؟

  • اختر Ethers.js إذا كنت تعمل على مشروع جديد أو تستخدم Hardhat.
  • اختر Web3.js إذا كنت تصون مشروعاً قديماً أو تحتاج تكاملاً قائماً عليها.
  • في الحالتين، ستحتاج إلى فهم ABI، العنوان، الشبكة، والمحفظة المتصلة.

ما الذي تحتاجه لربط الواجهة بالعقد الذكي؟

قبل كتابة أي سطر في الواجهة، يجب أن يكون العقد منشوراً على شبكة معروفة، سواء عبر محرر Remix IDE أو من خلال سكربتات النشر باستخدام Hardhat. كذلك تحتاج إلى ملف ABI الناتج من الترجمة، لأنه يمثل القاموس الذي يشرح للواجهة كيف تستدعي الدوال.

إذا كان العقد نفسه مبنياً وفق مفاهيم مثل الدوال في Solidity، والتعامل مع الأخطاء باستخدام require، فسيكون الربط أوضح وأسهل للاختبار من جهة الواجهة.

المكونات الأساسية لطبقة الربط

  1. مزود شبكة Provider.
  2. موقّع المعاملات Signer.
  3. واجهة العقد ABI.
  4. عنوان العقد contractAddress.
  5. إدارة حالة الواجهة مثل الحساب الحالي والشبكة ورسائل الخطأ.

مثال عقد ذكي بسيط للتكامل مع الواجهة

لنفترض أننا نريد ربط واجهة بعقد يخزن رسالة نصية ويصدر حدثاً عند التحديث. هذا النموذج ممتاز لفهم القراءة والكتابة والاستماع للأحداث في آن واحد.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract MessageBoard {
    string private message;
    address public owner;

    event MessageUpdated(address indexed updater, string newMessage);

    constructor(string memory initialMessage) {
        owner = msg.sender;
        message = initialMessage;
    }

    function getMessage() public view returns (string memory) {
        return message;
    }

    function setMessage(string calldata newMessage) external {
        message = newMessage;
        emit MessageUpdated(msg.sender, newMessage);
    }
}

هذا العقد يحتوي على دالة قراءة getMessage من النوع view، ودالة كتابة setMessage، بالإضافة إلى حدث MessageUpdated لإعلام الواجهة بأي تغيير.

الربط باستخدام Ethers.js

في الواجهات الحديثة، غالباً نبدأ بطلب إذن الاتصال بالمحفظة، ثم ننشئ BrowserProvider أو المزود المكافئ، وبعدها نستخرج Signer لتمريره إلى العقد.

// JavaScript example with Ethers.js
import { ethers } from "ethers";

const contractAddress = "0xYourContractAddress";
const abi = [
  "function getMessage() view returns (string)",
  "function setMessage(string newMessage)",
  "event MessageUpdated(address indexed updater, string newMessage)"
];

async function connectContract() {
  if (!window.ethereum) throw new Error("MetaMask not found");

  await window.ethereum.request({ method: "eth_requestAccounts" });

  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const contract = new ethers.Contract(contractAddress, abi, signer);

  return { provider, signer, contract };
}

async function readMessage() {
  const { contract } = await connectContract();
  const message = await contract.getMessage();
  console.log(message);
}

async function updateMessage(newValue) {
  const { contract } = await connectContract();
  const tx = await contract.setMessage(newValue);
  await tx.wait();
  console.log("Message updated");
}

رغم أن المثال السابق مكتوب بلغة JavaScript، إلا أن الفكرة الجوهرية واضحة: القراءة تتم مباشرة عبر العقد، أما الكتابة فتنشئ معاملة تنتظر التأكيد على الشبكة قبل تحديث الواجهة.

الربط باستخدام Web3.js

إن كنت تعمل على مشروع يعتمد Web3.js، فآلية الربط متشابهة، لكن بصياغة مختلفة قليلاً. ستنشئ كائناً من Web3 ثم تبني كائن العقد.

// JavaScript example with Web3.js
import Web3 from "web3";

const web3 = new Web3(window.ethereum);
const contractAddress = "0xYourContractAddress";
const abi = [
  {
    "inputs": [],
    "name": "getMessage",
    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
    "stateMutability": "view",
    "type": "function"
  }
];

const contract = new web3.eth.Contract(abi, contractAddress);

async function readMessage() {
  const result = await contract.methods.getMessage().call();
  console.log(result);
}

في هذه المقاربة، استدعاء call() يستخدم للقراءة، بينما send() يستخدم لتغيير حالة العقد وإرسال معاملة فعلية.

الفرق بين القراءة والكتابة من منظور الواجهة

هذه النقطة أساسية جداً في هندسة Web3 frontend. دوال القراءة لا تحتاج من المستخدم توقيعاً إذا كانت تعتمد على Provider عام، بينما دوال الكتابة تتطلب محفظة، موافقة، ورسوم Gas Fees.

  • القراءة: سريعة، لا تغير الحالة، مناسبة لجلب الرصيد أو الرسائل أو الإعدادات.
  • الكتابة: أبطأ، تحتاج توقيعاً، وقد تفشل بسبب الشبكة أو الصلاحيات أو نفاد الغاز.
  • الواجهة الذكية يجب أن تعرض حالات مثل pending وconfirmed وrejected.

لا تعتمد على نجاح المعاملة بمجرد استدعاء الدالة في الواجهة. يجب انتظار تأكيد tx.wait() أو عدد مناسب من التأكيدات قبل تحديث البيانات الحساسة، لأن المعاملة قد تُرفض أو تُستبدل أو تفشل أثناء التنفيذ.

الاستماع إلى الأحداث وتحديث الواجهة لحظياً

من أفضل مزايا الربط الاحترافي أن الواجهة لا تعتمد فقط على إعادة تحميل البيانات يدوياً. يمكنها الاستماع إلى Events الصادرة من العقد، وهو ما يجعل تجربة المستخدم أقرب إلى التطبيقات التقليدية، لكن فوق بنية لا مركزية.

عند انبعاث الحدث MessageUpdated مثلاً، يمكن للواجهة إعادة جلب الرسالة الحالية، أو تحديث الحالة المحلية مباشرة. هذه الآلية ضرورية خصوصاً في تطبيقات الرموز، الأسواق، والألعاب القائمة على NFTs.

اعتبارات الأمان وتجربة المستخدم

الواجهة ليست مجرد طبقة عرض؛ بل هي خط الدفاع الأول ضد الأخطاء التشغيلية للمستخدم. لذلك يجب التحقق من الشبكة الحالية، إظهار عنوان العقد الصحيح، وإدارة الاستثناءات القادمة من المحفظة أو العقد، خاصة إذا احتوى العقد على معدلات Modifiers أو صلاحيات إدارية.

لا تُخفِ رسائل الخطأ الحقيقية القادمة من العقد، خصوصاً عند استخدام require أو عند فشل تحقق الصلاحيات. عرض الرسائل المفهومة يساعد المستخدم ويقلل المعاملات الخاطئة، كما يحسن قابلية التدقيق واكتشاف العيوب المنطقية مبكراً.

ومن الناحية الأمنية أيضاً، يجب الانتباه إلى أن الواجهة لا تحل محل أمان العقد نفسه. حتى لو منعت زر السحب في UI، يبقى بإمكان أي طرف استدعاء العقد مباشرة إذا كانت الحماية البرمجية غائبة. لهذا يرتبط بناء الواجهة الجيدة دائماً بممارسات مثل استخدام OpenZeppelin، وفهم ثغرات مثل هجوم Reentrancy.

كيف تبني طبقة تكامل احترافية قابلة للتوسع؟

في المشاريع الجدية، لا تكتب منطق الاتصال بالعقد داخل كل زر أو مكوّن. الأفضل فصل طبقة الخدمة في ملفات مستقلة، مثل contractService أو web3Provider. هذا يجعل الاختبار أسهل، ويقلل التكرار، ويسمح بتبديل الشبكات أو العقود دون كسر الواجهة.

كما يُنصح بالاحتفاظ بملفات ABI والعناوين في إعدادات واضحة حسب البيئة: localhost، testnet، وmainnet. هذا مهم جداً عند الانتقال من التطوير المحلي إلى النشر الفعلي.

لتقليل استهلاك Gas من جهة تجربة المستخدم، اجعل الواجهة تستدعي دوال القراءة أولاً قبل إرسال المعاملة، واستخدم التقدير المسبق مثل estimateGas عندما يكون ذلك مناسباً، مع تصميم عقود تقلل الكتابات غير الضرورية على storage.

خاتمة

ربط الواجهة بالعقد الذكي ليس خطوة ثانوية في بناء تطبيقات Web3، بل هو قلب التجربة التي يراها المستخدم. من خلال Web3.js أو Ethers.js، تستطيع بناء قناة اتصال موثوقة تقرأ من البلوكتشين، تكتب إليه، وتتابع أحداثه الحية.

وعندما تُبنى هذه الطبقة فوق عقد مصمم جيداً ومختبر بعناية، تصبح الواجهة اللامركزية أكثر أماناً، أكثر وضوحاً، وأعلى جودة هندسية. وهذا هو الفارق الحقيقي بين نموذج تجريبي بسيط وبين تطبيق احترافي قابل للنشر والتوسع على شبكات الإنتاج.

3 comments

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *