مشروع تطبيقي: برمجة عقد ذكي لسك (Minting) مجموعة NFTs لصور فنية
مشروع تطبيقي: برمجة عقد ذكي لسك (Minting) مجموعة NFTs لصور فنية
يُعد بناء عقد ذكي لسك مجموعة صور فنية بصيغة NFT من أفضل التطبيقات العملية لفهم كيفية انتقال الأصول الرقمية من مجرد ملفات إلى عناصر قابلة للامتلاك والتداول على شبكة Blockchain. هذا المشروع لا يركز فقط على كتابة الكود، بل على تصميم منطق سك مدروس، آمن، وقابل للتكامل مع الواجهات الأمامية والمنصات الفنية.
إذا كنت قد قرأت سابقاً مدخل إلى Web3: ما هو البلوكتشين ولماذا يغير شكل الإنترنت والأنظمة المالية؟ فستدرك أن ملكية الأصل الرقمي لا تُحفظ في قاعدة بيانات تقليدية، بل داخل عقد ذكي على شبكة متوافقة مع EVM. كما أن فهم معيار ERC-721: ما هي الرموز غير القابلة للاستبدال (NFTs) برمجياً؟ مهم جداً قبل الدخول في التنفيذ الفعلي.
فكرة المشروع والبنية المعمارية
سننشئ عقداً ذكياً يسمح بسك عدد محدود من الصور الفنية، مع تحديد حد أقصى للمجموعة، وسعر سك لكل عنصر، وربط كل رمز ببيانات وصفية مخزنة خارج السلسلة عبر IPFS أو بوابة استضافة مخصصة. الهدف هنا هو استخدام معيار ERC-721 مع مكتبات OpenZeppelin لتقليل المخاطر ورفع موثوقية الكود.
البنية العامة للمشروع تتكون من:
- عقد ذكي لإدارة السك والملكية.
- ملفات
metadataلكل صورة. - صور فنية مرفوعة على
IPFS. - أداة نشر واختبار مثل
HardhatأوRemix. - واجهة أمامية لاحقة تتصل بالعقد عبر
Ethers.js.
التحضير البرمجي قبل كتابة العقد
قبل النشر، تأكد من إعداد محفظتك وربطها بشبكة اختبار من خلال إعداد بيئة التطوير: تثبيت محفظة MetaMask والاتصال بشبكات الاختبار (Testnets)، ثم احصل على رصيد تجريبي من خلال الحصول على عملات تجريبية مجانية (Faucet) للبدء في نشر واختبار العقود الذكية.
على مستوى التصميم، يجب اتخاذ قرارات مبكرة حول:
- هل السك سيكون عاماً أم محصوراً بالمدير؟
- هل جميع الرموز ستستخدم نفس
baseURI؟ - هل هناك حد أقصى لكل محفظة؟
- هل يوجد إيقاف مؤقت لعملية السك عبر
pause؟
هذه القرارات تؤثر مباشرة على البنية المنطقية للعقد وعلى استهلاك التكاليف (Gas Fees): كيف يحسب البلوكتشين تكلفة تنفيذ الأكواد؟.
العقد الذكي الكامل لسك مجموعة الصور الفنية
سنستخدم الوراثة من مكتبات آمنة ومختبرة كما شرحنا في استخدام مكتبة OpenZeppelin لكتابة عقود ذكية آمنة ومختبرة مسبقاً. كما سنستفيد من مفاهيم المعدلات (Modifiers): حماية الدوال برمجياً والتعامل مع الأخطاء وإرجاع الأموال: استخدام require, assert, revert.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract ArtNFTCollection is ERC721URIStorage, Ownable {
using Strings for uint256;
uint256 public mintPrice = 0.01 ether;
uint256 public maxSupply = 100;
uint256 public totalMinted;
bool public mintEnabled = true;
mapping(address => uint256) public walletMints;
uint256 public maxPerWallet = 3;
event NFTMinted(address indexed minter, uint256 indexed tokenId, string tokenURISet);
event MintStatusUpdated(bool enabled);
event MintPriceUpdated(uint256 newPrice);
event FundsWithdrawn(address indexed owner, uint256 amount);
constructor() ERC721("Art Image Collection", "ARTIMG") Ownable(msg.sender) {}
function mintNFT(string memory metadataURI) external payable {
require(mintEnabled, "Minting is disabled");
require(msg.value >= mintPrice, "Insufficient payment");
require(totalMinted < maxSupply, "Max supply reached");
require(walletMints[msg.sender] < maxPerWallet, "Wallet mint limit reached");
require(bytes(metadataURI).length > 0, "Metadata URI is required");
uint256 tokenId = totalMinted + 1;
totalMinted = tokenId;
walletMints[msg.sender] += 1;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, metadataURI);
emit NFTMinted(msg.sender, tokenId, metadataURI);
}
function setMintEnabled(bool _enabled) external onlyOwner {
mintEnabled = _enabled;
emit MintStatusUpdated(_enabled);
}
function setMintPrice(uint256 _newPrice) external onlyOwner {
mintPrice = _newPrice;
emit MintPriceUpdated(_newPrice);
}
function setMaxPerWallet(uint256 _newLimit) external onlyOwner {
require(_newLimit > 0, "Limit must be greater than zero");
maxPerWallet = _newLimit;
}
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No funds to withdraw");
(bool success, ) = payable(owner()).call{value: balance}("");
require(success, "Withdraw failed");
emit FundsWithdrawn(owner(), balance);
}
function remainingSupply() external view returns (uint256) {
return maxSupply - totalMinted;
}
}
شرح المكونات الداخلية للعقد
الوراثة والمكتبات
العقد يرث من ERC721URIStorage لتخزين رابط البيانات الوصفية لكل رمز، ومن Ownable لتقييد بعض الدوال للمالك فقط. مفهوم الوراثة نفسه تم شرحه في الوراثة (Inheritance): بناء عقود ذكية متقدمة بالاعتماد على أكواد عقود سابقة.
إدارة الحالة
المتغيرات مثل mintPrice وtotalMinted وwalletMints تمثل State Variables وMappings تُخزن على السلسلة. ويمكنك مراجعة أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables) والقواميس (Mappings): أسرع طريقة لربط عناوين المحافظ بأرصدتها (Key-Value).
منطق السك
دالة mintNFT تتحقق من أربعة شروط أساسية: تفعيل السك، كفاية الدفع، عدم تجاوز المعروض، وعدم تجاوز حد المحفظة. بعد ذلك يتم إنشاء tokenId جديد وربطه بعنوان المالك وملف البيانات الوصفية.
من المهم أيضاً أن إطلاق حدث NFTMinted يجعل الواجهة الأمامية تلتقط العملية فوراً، وهو امتداد عملي لمفهوم الأحداث (Events): كيف يخبر العقد الذكي واجهة الموقع (React) بأن شيئاً ما قد حدث؟.
أمنياً، لا تعتمد على رفع الصور نفسها داخل العقد الذكي، لأن تخزين البيانات الكبيرة على السلسلة مكلف جداً وغير عملي. الأفضل تخزين رابط
URIفقط، مع حفظ الصور وملفاتJSON metadataعبرIPFSأو طبقة تخزين لامركزية موثوقة.
كيف تختبر وتنشر العقد؟
يمكنك تجربة العقد سريعاً عبر محرر Remix IDE: كتابة ونشر أول عقد ذكي (Smart Contract) على المتصفح مباشرة، لكن في المشاريع الواقعية يُفضل استخدام Hardhat لكتابة اختبارات وحدات ونشر منظم.
- أنشئ مشروع
Node.jsوثبّتHardhatو@openzeppelin/contracts. - أضف العقد داخل مجلد
contracts. - اكتب اختبارات للتحقق من حدود السك، الدفعات، والسحب.
- انشر على شبكة اختبار واربط العقد بواجهة أمامية عبر
Ethers.js.
تحسينات متقدمة للمشروع
بعد نجاح النسخة الأولى، يمكنك إضافة خصائص أكثر احترافية:
- قائمة سماح
Whitelistللسك المبكر. - آلية
Revealلإخفاء الصور قبل الإطلاق الرسمي. - إضافة نسبة
Royaltyوفق معيارERC-2981. - إتاحة السك على دفعات لتقليل الضغط على العقد.
لخفض استهلاك
Gas، فكّر في استخدامbaseURIموحد بدلاً من تخزينtokenURIكامل لكل رمز، واستخدم الدوال المصنفةviewحين لا تحتاج إلى تعديل الحالة، كما تم توضيحه في أنواع الدوال : فهم view و pure لتوفير رسوم الـ Gas.
أخطاء شائعة يجب تجنبها
- توليد الصور أو البيانات العشوائية داخل العقد بطريقة غير آمنة.
- ترك دوال إدارية حساسة دون حماية
onlyOwner. - إهمال التحقق من قيمة
msg.valueفي دوال السك المدفوعة. - ربط الواجهة بعقد غير مختبر على شبكة اختبار.
كما أن فهم استلام وإرسال الأموال (Ether) برمجياً: فهم الدوال payable و fallback ضروري لأن أي عقد سك مدفوع يتعامل مباشرة مع استقبال الأموال وسحبها لاحقاً.
الخاتمة
هذا المشروع يضعك على المسار العملي لبناء مجموعة فنية قائمة على NFTs بطريقة احترافية، بدءاً من منطق السك، مروراً بحفظ البيانات الوصفية، وانتهاءً باعتبارات الأمان وتحسين التكلفة. القوة الحقيقية هنا ليست في نشر عقد يعمل فقط، بل في فهم كيف تبني منتجاً قابلاً للتوسع والتكامل مع منظومة Web3 بكفاءة وموثوقية.
بعد إتقان هذا النموذج، يمكنك الانتقال إلى واجهة بيع كاملة، ولوحة إدارة للفنان، وربط العقد بسوق ثانوي أو حتى تطوير مجموعات أكثر تعقيداً تجمع بين الفن والمنفعة الرقمية داخل التطبيقات اللامركزية DApps.