قراءة البيانات من البلوكتشين وعرضها في واجهة الموقع مجاناً

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

قراءة البيانات من البلوكتشين وعرضها في واجهة الموقع مجاناً

أحد أكثر الأسئلة العملية في تطوير تطبيقات Web3
هو: كيف نعرض بيانات العقد الذكي داخل الموقع بدون دفع رسوم في كل مرة؟ الإجابة التقنية تبدأ من فهم الفرق بين
القراءة والكتابة على Blockchain.
فالدوال التي تقرأ فقط، مثل view،
يمكن استدعاؤها من الواجهة عبر مزوّد RPC دون إنشاء معاملة فعلية.

هذا يعني أنك تستطيع جلب اسم منتج، رصيد مستخدم، عدد العناصر، أو بيانات
Struct
من الشبكة وعرضها مباشرة داخل الصفحة مجاناً تقريباً من جهة المستخدم. وإذا كنت تحتاج أساساً مفاهيمياً أوسع،
فمن المفيد مراجعة مدخل إلى Web3: ما هو البلوكتشين ولماذا يغير شكل الإنترنت والأنظمة المالية؟
قبل الانتقال إلى مستوى الربط البرمجي.

ما معنى القراءة المجانية من البلوكتشين؟

عندما تستدعي دالة موسومة بـ
view
أو pure،
فإن الطلب يُنفَّذ محلياً على عقدة مزوّد الشبكة عبر
eth_call
بدلاً من إرساله كمعاملة تحتاج توقيعاً ورسوم
Gas Fees.
ولهذا السبب تبدو عملية القراءة فورية نسبياً ولا تخصم رصيداً من محفظة الزائر.

هذا المفهوم مرتبط مباشرة بمقال
أنواع الدوال : فهم view و pure لتوفير رسوم الـ Gas،
لأن نجاح الواجهة في عرض البيانات مجاناً يعتمد على تصميم العقد منذ البداية بطريقة تميّز بين دوال القراءة ودوال التعديل.
أما إذا كانت الدالة تغيّر حالة العقد، فسيصبح الاستدعاء معاملة مدفوعة وليس قراءة مجانية.

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

قبل كتابة أي كود في الواجهة، يجب أن يكون العقد الذكي منظماً ويقدم دوال قراءة واضحة. من الأخطاء الشائعة أن يضع المطور
البيانات داخل تراكيب معقدة ثم لا يوفّر دوال استرجاع مناسبة. لذلك من الأفضل تعريف
State Variables
ودوال استعلام صريحة يمكن للواجهة استهلاكها بسهولة.

إذا كنت قد درست
أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables)
والدوال (Functions) في Solidity: من يمكنه قراءة وتعديل بيانات العقد؟،
فستلاحظ أن أفضل العقود القابلة للعرض الأمامي هي تلك التي تفصل منطق التخزين عن منطق الإظهار.

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

contract ProductRegistry {
    struct Product {
        uint256 id;
        string name;
        uint256 price;
        address owner;
        bool active;
    }

    Product[] private products;

    constructor() {
        products.push(Product(1, "Laptop", 1200 ether, msg.sender, true));
        products.push(Product(2, "Phone", 800 ether, msg.sender, true));
    }

    function addProduct(
        string memory _name,
        uint256 _price,
        address _owner
    ) external {
        uint256 newId = products.length + 1;
        products.push(Product(newId, _name, _price, _owner, true));
    }

    function getProduct(uint256 _index) external view returns (
        uint256 id,
        string memory name,
        uint256 price,
        address owner,
        bool active
    ) {
        Product memory p = products[_index];
        return (p.id, p.name, p.price, p.owner, p.active);
    }

    function getProductsCount() external view returns (uint256) {
        return products.length;
    }
}

في هذا المثال أنشأنا عقداً بسيطاً يعرض عدد المنتجات ثم يعيد تفاصيل كل عنصر بدالة
getProduct.
استخدام Structs
يسهّل تنظيم البيانات، وهو امتداد عملي لما شرحناه في
الهياكل (Structs): تصميم أنواع بيانات مخصصة (مثل: كائن يمثل موظف أو منتج).
كما أن تخزين العناصر داخل Arrays
يجعل الفهرسة والعدّ مباشرين، وهو مرتبط بمقال
المصفوفات (Arrays) في Solidity: تخزين وإدارة قوائم البيانات داخل العقد الذكي.

عند تصميم دوال القراءة، لا تُرجع كميات هائلة من البيانات دفعة واحدة إذا كان حجمها قابلاً للنمو. رغم أن المستخدم لا يدفع رسوماً مباشرة في eth_call، إلا أن الاستجابة قد تصبح بطيئة أو تتجاوز حدود مزوّد RPC. الأفضل استخدام pagination أو دوال تعيد العناصر حسب الفهرس.

كيف تقرأ الواجهة البيانات باستخدام Ethers.js؟

في الطبقة الأمامية نحتاج إلى ثلاثة عناصر: عنوان العقد، وواجهة
ABI،
ومزوّد اتصال بالشبكة. إذا كنت تعمل ضمن بيئة احترافية، فابدأ من
إعداد واجهة React.js وتثبيت مكتبة Ethers.js للاتصال بالبلوكتشين
ثم انتقل إلى
هندسة الويب اللامركزي (Web3.js & Ethers.js): كيف نربط الواجهات بالعقود الذكية؟.

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

const provider = new ethers.JsonRpcProvider("https://rpc.ankr.com/eth_sepolia");
const contractAddress = "0xYourContractAddress";
const abi = [
  "function getProductsCount() view returns (uint256)",
  "function getProduct(uint256 _index) view returns (uint256 id, string name, uint256 price, address owner, bool active)"
];

const contract = new ethers.Contract(contractAddress, abi, provider);

async function loadProducts() {
  const count = await contract.getProductsCount();
  const items = [];

  for (let i = 0; i < Number(count); i++) {
    const product = await contract.getProduct(i);
    items.push({
      id: product.id.toString(),
      name: product.name,
      price: product.price.toString(),
      owner: product.owner,
      active: product.active
    });
  }

  return items;
}

رغم أن هذا كود JavaScript،
فقد وضعته داخل تنسيق الكود المطلوب ليتوافق مع بنية النشر. الفكرة الجوهرية هي أن
provider
يمثل قناة القراءة، بينما
contract
هو الكائن الذي يتيح استدعاء دوال العقد كما لو كانت دوال عادية داخل التطبيق.

عرض البيانات داخل الصفحة

بعد استرجاع البيانات، تُحوَّل إلى عناصر
HTML
أو مكونات React.
في هذه المرحلة تظهر أهمية تنسيق البيانات، لأن القيم العددية في
EVM
تعود غالباً على شكل
BigInt
أو قيم كبيرة تحتاج تحويل قبل عرضها للمستخدم.

  • حوّل القيم الرقمية باستخدام toString() أو أدوات التنسيق المناسبة.
  • إذا كانت القيمة بوحدة wei فاستخدم دالة تحويل مثل formatEther.
  • اعرض العنوان address مختصراً لتحسين تجربة الاستخدام.
  • استخدم حالة تحميل loading state لأن القراءة من الشبكة ليست لحظية دائماً.

هل نحتاج إلى MetaMask لعرض البيانات؟

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

أفضل ممارسات الأداء والأمان

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

لا تعتمد على بيانات الواجهة وحدها لاتخاذ قرارات أمنية. حتى لو عرضت دالة view أن المستخدم يملك صلاحية معينة، يجب فرض الصلاحية نفسها داخل العقد باستخدام آليات مثل modifiers وrequire. للمزيد راجع المعدلات (Modifiers): حماية الدوال برمجياً والتعامل مع الأخطاء وإرجاع الأموال: استخدام require, assert, revert.

  • قلّل عدد الاستدعاءات المتكررة باستخدام التخزين المؤقت caching في الواجهة.
  • اجمع القراءات منطقياً بدوال مصممة جيداً بدلاً من عشرات الاستدعاءات الصغيرة غير الضرورية.
  • احذر من حلقات for الكبيرة داخل العقد عند بناء دوال الاسترجاع.
  • فعّل الأحداث Events لتحديث الواجهة بعد التعديلات بدلاً من إعادة تحميل كل شيء باستمرار، ويمكن الاستفادة من الأحداث (Events): كيف يخبر العقد الذكي واجهة الموقع (React) بأن شيئاً ما قد حدث؟.

الخلاصة

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

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

10 comments

اترك تعليقاً

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