دليل تعليمي لأداة سك الرموز غير القابلة للاستبدال (NFT Minter)
أحد أكبر التحديات التي تواجه المطورين القادمين من خلفية Web2 هو معرفة كيفية ربط العقد الذكي بمشروع واجهة أمامية والتفاعل معه.
من خلال بناء أداة سك الرموز غير القابلة للاستبدال (NFT minter) — وهي واجهة مستخدم بسيطة حيث يمكنك إدخال رابط لأصلك الرقمي، وعنوان، ووصف — ستتعلم كيفية:
- الاتصال بـ MetaMask عبر مشروع الواجهة الأمامية الخاص بك
- استدعاء دوال العقد الذكي من الواجهة الأمامية
- توقيع المعاملات باستخدام MetaMask
في هذا الدليل التعليمي، سنستخدم React (opens in a new tab) كإطار عمل للواجهة الأمامية. نظرًا لأن هذا الدليل يركز بشكل أساسي على تطوير Web3، فلن نقضي الكثير من الوقت في تفصيل أساسيات React. بدلاً من ذلك، سنركز على إضافة الوظائف إلى مشروعنا.
كمتطلب أساسي، يجب أن يكون لديك فهم بمستوى مبتدئ لـ React—أن تعرف كيف تعمل المكونات (components)، والخصائص (props)، و useState/useEffect، واستدعاء الدوال الأساسية. إذا لم تسمع بأي من هذه المصطلحات من قبل، فقد ترغب في الاطلاع على دليل مقدمة إلى React (opens in a new tab). بالنسبة للمتعلمين البصريين، نوصي بشدة بسلسلة مقاطع الفيديو الممتازة Full Modern React Tutorial (opens in a new tab) بواسطة Net Ninja.
وإذا لم تكن قد فعلت ذلك بالفعل، فستحتاج بالتأكيد إلى حساب Alchemy لإكمال هذا الدليل التعليمي بالإضافة إلى بناء أي شيء على البلوك تشين. قم بالتسجيل للحصول على حساب مجاني هنا (opens in a new tab).
بدون مزيد من التأخير، دعونا نبدأ!
أساسيات إنشاء رموز NFT
قبل أن نبدأ في النظر إلى أي كود، من المهم فهم كيفية عمل إنشاء رمز NFT. يتضمن ذلك خطوتين:
نشر عقد ذكي لـ NFT على البلوك تشين الخاص بإيثريوم
أكبر اختلاف بين معياري العقود الذكية لـ NFT هو أن ERC-1155 هو معيار متعدد الرموز ويتضمن وظيفة الدفعات (batch functionality)، بينما ERC-721 هو معيار لرمز واحد وبالتالي يدعم فقط نقل رمز واحد في كل مرة.
استدعاء دالة السك
عادةً، تتطلب دالة السك هذه تمرير متغيرين كمعلمات (parameters)، الأول هو recipient، والذي يحدد العنوان الذي سيتلقى رمز NFT الذي تم سكه حديثًا، والثاني هو tokenURI الخاص بـ NFT، وهو سلسلة نصية (string) تشير إلى مستند JSON يصف البيانات الوصفية (metadata) لرمز NFT.
البيانات الوصفية لرمز NFT هي ما يجعله حيًا حقًا، حيث تسمح له بامتلاك خصائص، مثل الاسم، والوصف، والصورة (أو أصل رقمي مختلف)، وسمات أخرى. إليك مثال على tokenURI (opens in a new tab)، والذي يحتوي على البيانات الوصفية لرمز NFT.
في هذا الدليل التعليمي، سنركز على الجزء الثاني، وهو استدعاء دالة السك لعقد ذكي موجود لـ NFT باستخدام واجهة مستخدم React الخاصة بنا.
إليك رابط (opens in a new tab) للعقد الذكي ERC-721 NFT الذي سنقوم باستدعائه في هذا الدليل التعليمي. إذا كنت ترغب في معرفة كيف قمنا بإنشائه، نوصي بشدة بالاطلاع على دليلنا التعليمي الآخر، "كيفية إنشاء NFT" (opens in a new tab).
رائع، الآن بعد أن فهمنا كيفية عمل إنشاء NFT، دعونا نستنسخ ملفات البداية الخاصة بنا!
استنساخ ملفات البداية
أولاً، انتقل إلى مستودع GitHub الخاص بـ nft-minter-tutorial (opens in a new tab) للحصول على ملفات البداية لهذا المشروع. قم باستنساخ هذا المستودع في بيئتك المحلية.
عندما تفتح مستودع nft-minter-tutorial المستنسخ هذا، ستلاحظ أنه يحتوي على مجلدين: minter-starter-files و nft-minter.
- يحتوي
minter-starter-filesعلى ملفات البداية (بشكل أساسي واجهة مستخدم React) لهذا المشروع. في هذا الدليل التعليمي، سنعمل في هذا الدليل، حيث ستتعلم كيفية إحياء واجهة المستخدم هذه عن طريق ربطها بمحفظة إيثريوم الخاصة بك وعقد ذكي لـ NFT. - يحتوي
nft-minterعلى الدليل التعليمي المكتمل بالكامل وهو موجود كـ مرجع لك إذا واجهت أي صعوبة.
بعد ذلك، افتح نسختك من minter-starter-files في محرر الأكواد الخاص بك، ثم انتقل إلى مجلد src.
جميع الأكواد التي سنكتبها ستكون داخل مجلد src. سنقوم بتعديل المكون Minter.js وكتابة ملفات JavaScript إضافية لمنح مشروعنا وظائف Web3.
الخطوة 2: التحقق من ملفات البداية
قبل أن نبدأ في كتابة الأكواد، من المهم التحقق مما تم توفيره لنا بالفعل في ملفات البداية.
تشغيل مشروع React الخاص بك
دعونا نبدأ بتشغيل مشروع React في متصفحنا. جمال React هو أنه بمجرد تشغيل مشروعنا في المتصفح، سيتم تحديث أي تغييرات نحفظها مباشرة في المتصفح.
لتشغيل المشروع، انتقل إلى الدليل الجذري لمجلد minter-starter-files، وقم بتشغيل npm install في الطرفية (terminal) لتثبيت تبعيات المشروع:
cd minter-starter-filesnpm installبمجرد الانتهاء من التثبيت، قم بتشغيل npm start في الطرفية:
npm startالقيام بذلك يجب أن يفتح http://localhost:3000/ (opens in a new tab) في متصفحك، حيث سترى الواجهة الأمامية لمشروعنا. يجب أن تتكون من 3 حقول: مكان لإدخال رابط لأصل NFT الخاص بك، وإدخال اسم NFT الخاص بك، وتقديم وصف.
إذا حاولت النقر على زري "Connect Wallet" أو "Mint NFT"، ستلاحظ أنهما لا يعملان—وذلك لأننا لا نزال بحاجة إلى برمجة وظائفهما! :)
المكون Minter.js
ملاحظة: تأكد من أنك في مجلد minter-starter-files وليس في مجلد nft-minter!
دعونا نعود إلى مجلد src في محررنا ونفتح ملف Minter.js. من المهم جدًا أن نفهم كل شيء في هذا الملف، لأنه مكون React الأساسي الذي سنعمل عليه.
في الجزء العلوي من هذا الملف، لدينا متغيرات الحالة (state variables) التي سنقوم بتحديثها بعد أحداث معينة.
1//State variables // متغيرات الحالة2const [walletAddress, setWallet] = useState("")3const [status, setStatus] = useState("")4const [name, setName] = useState("")5const [description, setDescription] = useState("")6const [url, setURL] = useState("")لم تسمع أبدًا عن متغيرات الحالة في React أو خطافات الحالة (state hooks)؟ تحقق من هذه (opens in a new tab) المستندات.
إليك ما يمثله كل من المتغيرات:
walletAddress- سلسلة نصية تخزن عنوان محفظة المستخدمstatus- سلسلة نصية تحتوي على رسالة لعرضها في أسفل واجهة المستخدمname- سلسلة نصية تخزن اسم NFTdescription- سلسلة نصية تخزن وصف NFTurl- سلسلة نصية تمثل رابطًا للأصل الرقمي لـ NFT
بعد متغيرات الحالة، سترى ثلاث دوال غير منفذة: useEffect، و connectWalletPressed، و onMintPressed. ستلاحظ أن جميع هذه الدوال هي async، وذلك لأننا سنجري استدعاءات API غير متزامنة بداخلها! أسماؤها تدل على وظائفها:
1useEffect(async () => {2 //TODO: implement // للقيام به: التنفيذ3}, [])45const connectWalletPressed = async () => {6 //TODO: implement // للقيام به: التنفيذ7}89const onMintPressed = async () => {10 //TODO: implement // للقيام به: التنفيذ11}إظهار الكلuseEffect(opens in a new tab) - هذا خطاف React يتم استدعاؤه بعد تصيير (render) المكون الخاص بك. نظرًا لأنه يتم تمرير مصفوفة فارغة[]إليه (انظر السطر 3)، فسيتم استدعاؤه فقط في التصيير الأول للمكون. هنا سنستدعي مستمع المحفظة (wallet listener) ودالة محفظة أخرى لتحديث واجهة المستخدم الخاصة بنا لتعكس ما إذا كانت المحفظة متصلة بالفعل.connectWalletPressed- سيتم استدعاء هذه الدالة لربط محفظة MetaMask الخاصة بالمستخدم بالتطبيق اللامركزي (dapp) الخاص بنا.onMintPressed- سيتم استدعاء هذه الدالة لسك رمز NFT الخاص بالمستخدم.
بالقرب من نهاية هذا الملف، لدينا واجهة المستخدم الخاصة بمكوننا. إذا قمت بفحص هذا الكود بعناية، ستلاحظ أننا نقوم بتحديث متغيرات الحالة url، و name، و description عندما يتغير الإدخال في الحقول النصية المقابلة لها.
سترى أيضًا أنه يتم استدعاء connectWalletPressed و onMintPressed عند النقر على الأزرار ذات المعرفات mintButton و walletButton على التوالي.
1//the UI of our component // واجهة المستخدم للمكون الخاص بنا2return (3 <div className="Minter">4 <button id="walletButton" onClick={connectWalletPressed}>5 {walletAddress.length > 0 ? (6 "Connected: " +7 String(walletAddress).substring(0, 6) +8 "..." +9 String(walletAddress).substring(38)10 ) : (11 <span>Connect Wallet</span>12 )}13 </button>1415 <br></br>16 <h1 id="title">🧙♂️ Alchemy NFT Minter</h1>17 <p>18 Simply add your asset's link, name, and description, then press "Mint."19 </p>20 <form>21 <h2>🖼 Link to asset: </h2>22 <input23 type="text"24 placeholder="e.g., https://gateway.pinata.cloud/ipfs/<hash>"25 onChange={(event) => setURL(event.target.value)}26 />27 <h2>🤔 Name: </h2>28 <input29 type="text"30 placeholder="e.g., My first NFT!"31 onChange={(event) => setName(event.target.value)}32 />33 <h2>✍️ Description: </h2>34 <input35 type="text"36 placeholder="e.g., Even cooler than cryptokitties ;)"37 onChange={(event) => setDescription(event.target.value)}38 />39 </form>40 <button id="mintButton" onClick={onMintPressed}>41 Mint NFT42 </button>43 <p id="status">{status}</p>44</div>45)إظهار الكلأخيرًا، دعونا نتناول أين يتم إضافة مكون Minter هذا.
إذا ذهبت إلى ملف App.js، وهو المكون الرئيسي في React الذي يعمل كحاوية لجميع المكونات الأخرى، سترى أن مكون Minter الخاص بنا يتم حقنه في السطر 7.
في هذا الدليل التعليمي، سنقوم فقط بتعديل ملف Minter.js وإضافة ملفات في مجلد src الخاص بنا.
الآن بعد أن فهمنا ما نعمل عليه، دعونا نعد محفظة إيثريوم الخاصة بنا!
إعداد محفظة إيثريوم الخاصة بك
لكي يتمكن المستخدمون من التفاعل مع عقدك الذكي، سيحتاجون إلى ربط محفظة إيثريوم الخاصة بهم بتطبيقك اللامركزي.
تنزيل MetaMask
في هذا الدليل التعليمي، سنستخدم MetaMask، وهي محفظة افتراضية في المتصفح تُستخدم لإدارة عنوان حساب إيثريوم الخاص بك. إذا كنت ترغب في فهم المزيد حول كيفية عمل المعاملات على إيثريوم، تحقق من هذه الصفحة.
يمكنك تنزيل وإنشاء حساب MetaMask مجانًا هنا (opens in a new tab). عند إنشاء حساب، أو إذا كان لديك حساب بالفعل، تأكد من التبديل إلى "شبكة الاختبار Ropsten" (Ropsten Test Network) في الجزء العلوي الأيمن (حتى لا نتعامل بأموال حقيقية).
إضافة إيثر من صنبور
من أجل سك رموز NFT الخاصة بنا (أو توقيع أي معاملات على البلوك تشين الخاص بإيثريوم)، سنحتاج إلى بعض عملات ETH الوهمية. للحصول على ETH، يمكنك الذهاب إلى صنبور Ropsten (opens in a new tab) وإدخال عنوان حساب Ropsten الخاص بك، ثم النقر على "Send Ropsten Eth". يجب أن ترى ETH في حساب MetaMask الخاص بك بعد فترة وجيزة!
التحقق من رصيدك
للتأكد من وجود رصيدنا، دعونا نجري طلب eth_getBalance (opens in a new tab) باستخدام أداة الملحن الخاصة بـ Alchemy (opens in a new tab). سيعيد هذا مقدار ETH في محفظتنا. بعد إدخال عنوان حساب MetaMask الخاص بك والنقر على "Send Request"، يجب أن ترى استجابة مثل هذه:
1{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}ملاحظة: هذه النتيجة بوحدة wei وليس eth. تُستخدم wei كأصغر فئة من الإيثر. التحويل من wei إلى eth هو: 1 eth = 10¹⁸ wei. لذا إذا قمنا بتحويل 0xde0b6b3a7640000 إلى النظام العشري نحصل على 1*10¹⁸ والذي يساوي 1 eth.
يا للعجب! أموالنا الوهمية كلها موجودة!
ربط MetaMask بواجهة المستخدم الخاصة بك
الآن بعد إعداد محفظة MetaMask الخاصة بنا، دعونا نربط تطبيقنا اللامركزي بها!
لأننا نريد الالتزام بنموذج MVC (opens in a new tab)، سنقوم بإنشاء ملف منفصل يحتوي على دوالنا لإدارة المنطق، والبيانات، وقواعد تطبيقنا اللامركزي، ثم تمرير تلك الدوال إلى الواجهة الأمامية (مكون Minter.js الخاص بنا).
الدالة connectWallet
للقيام بذلك، دعونا ننشئ مجلدًا جديدًا يسمى utils في دليل src الخاص بك ونضيف ملفًا يسمى interact.js بداخله، والذي سيحتوي على جميع دوال التفاعل مع المحفظة والعقد الذكي.
في ملف interact.js الخاص بنا، سنكتب دالة connectWallet، والتي سنقوم بعد ذلك باستيرادها واستدعائها في مكون Minter.js الخاص بنا.
في ملف interact.js الخاص بك، أضف ما يلي:
1export const connectWallet = async () => {2 if (window.ethereum) {3 try {4 const addressArray = await window.ethereum.request({5 method: "eth_requestAccounts",6 })7 const obj = {8 status: "👆🏽 Write a message in the text-field above.",9 address: addressArray[0],10 }11 return obj12 } catch (err) {13 return {14 address: "",15 status: "😥 " + err.message,16 }17 }18 } else {19 return {20 address: "",21 status: (22 <span>23 <p>24 {" "}25 🦊 <a target="_blank" href={`https://metamask.io/download`}>26 You must install MetaMask, a virtual Ethereum wallet, in your27 browser.28 </a>29 </p>30 </span>31 ),32 }33 }34}إظهار الكلدعونا نفصل ما يفعله هذا الكود:
أولاً، تتحقق دالتنا مما إذا كان window.ethereum ممكّنًا في متصفحك.
window.ethereum هي واجهة برمجة تطبيقات (API) عالمية يتم حقنها بواسطة MetaMask وموفري المحافظ الآخرين والتي تسمح لمواقع الويب بطلب حسابات إيثريوم الخاصة بالمستخدمين. إذا تمت الموافقة، يمكنها قراءة البيانات من شبكات البلوك تشين التي يتصل بها المستخدم، واقتراح أن يقوم المستخدم بتوقيع الرسائل والمعاملات. تحقق من مستندات MetaMask (opens in a new tab) لمزيد من المعلومات!
إذا كان window.ethereum غير موجود، فهذا يعني أن MetaMask غير مثبت. ينتج عن هذا إرجاع كائن JSON، حيث يكون address المُرجع عبارة عن سلسلة نصية فارغة، وينقل كائن JSX status رسالة تفيد بأنه يجب على المستخدم تثبيت MetaMask.
معظم الدوال التي نكتبها ستعيد كائنات JSON يمكننا استخدامها لتحديث متغيرات الحالة وواجهة المستخدم الخاصة بنا.
الآن إذا كان window.ethereum موجودًا، فهنا تصبح الأمور مثيرة للاهتمام.
باستخدام حلقة try/catch، سنحاول الاتصال بـ MetaMask عن طريق استدعاء window.ethereum.request({ method: "eth_requestAccounts" }); (opens in a new tab). سيؤدي استدعاء هذه الدالة إلى فتح MetaMask في المتصفح، حيث سيُطلب من المستخدم ربط محفظته بتطبيقك اللامركزي.
- إذا اختار المستخدم الاتصال، فإن
method: "eth_requestAccounts"ستعيد مصفوفة تحتوي على جميع عناوين حسابات المستخدم المتصلة بالتطبيق اللامركزي. إجمالاً، ستعيد دالةconnectWalletالخاصة بنا كائن JSON يحتوي علىaddressالأول في هذه المصفوفة (انظر السطر 9) ورسالةstatusتطالب المستخدم بكتابة رسالة إلى العقد الذكي. - إذا رفض المستخدم الاتصال، فسيحتوي كائن JSON على سلسلة نصية فارغة لـ
addressالمُرجع ورسالةstatusتعكس أن المستخدم رفض الاتصال.
إضافة الدالة connectWallet إلى مكون واجهة المستخدم Minter.js
الآن بعد أن كتبنا دالة connectWallet هذه، دعونا نربطها بمكون Minter.js الخاص بنا.
أولاً، سيتعين علينا استيراد دالتنا إلى ملف Minter.js الخاص بنا عن طريق إضافة import { connectWallet } from "./utils/interact.js"; إلى الجزء العلوي من ملف Minter.js. يجب أن تبدو الأسطر الـ 11 الأولى من Minter.js الآن هكذا:
1import { useEffect, useState } from "react";2import { connectWallet } from "./utils/interact.js";34const Minter = (props) => {56 //State variables // متغيرات الحالة7 const [walletAddress, setWallet] = useState("");8 const [status, setStatus] = useState("");9 const [name, setName] = useState("");10 const [description, setDescription] = useState("");11 const [url, setURL] = useState("");إظهار الكلثم، داخل دالة connectWalletPressed الخاصة بنا، سنستدعي دالة connectWallet المستوردة، هكذا:
1const connectWalletPressed = async () => {2 const walletResponse = await connectWallet()3 setStatus(walletResponse.status)4 setWallet(walletResponse.address)5}هل تلاحظ كيف يتم تجريد معظم وظائفنا بعيدًا عن مكون Minter.js الخاص بنا من ملف interact.js؟ هذا لكي نتوافق مع نموذج M-V-C!
في connectWalletPressed، نقوم ببساطة بإجراء استدعاء await لدالة connectWallet المستوردة، وباستخدام استجابتها، نقوم بتحديث متغيرات status و walletAddress الخاصة بنا عبر خطافات الحالة الخاصة بها.
الآن، دعونا نحفظ كلا الملفين Minter.js و interact.js ونختبر واجهة المستخدم الخاصة بنا حتى الآن.
افتح متصفحك على localhost:3000، واضغط على زر "Connect Wallet" في أعلى يمين الصفحة.
إذا كان لديك MetaMask مثبتًا، فسيُطلب منك ربط محفظتك بتطبيقك اللامركزي. اقبل دعوة الاتصال.
يجب أن ترى أن زر المحفظة يعكس الآن أن عنوانك متصل.
بعد ذلك، حاول تحديث الصفحة... هذا غريب. يطالبنا زر المحفظة الخاص بنا بربط MetaMask، على الرغم من أنه متصل بالفعل...
لا تقلق مع ذلك! يمكننا إصلاح ذلك بسهولة عن طريق تنفيذ دالة تسمى getCurrentWalletConnected، والتي ستتحقق مما إذا كان العنوان متصلاً بالفعل بتطبيقنا اللامركزي وتحديث واجهة المستخدم الخاصة بنا وفقًا لذلك!
الدالة getCurrentWalletConnected
في ملف interact.js الخاص بك، أضف دالة getCurrentWalletConnected التالية:
1export const getCurrentWalletConnected = async () => {2 if (window.ethereum) {3 try {4 const addressArray = await window.ethereum.request({5 method: "eth_accounts",6 })7 if (addressArray.length > 0) {8 return {9 address: addressArray[0],10 status: "👆🏽 Write a message in the text-field above.",11 }12 } else {13 return {14 address: "",15 status: "🦊 Connect to MetaMask using the top right button.",16 }17 }18 } catch (err) {19 return {20 address: "",21 status: "😥 " + err.message,22 }23 }24 } else {25 return {26 address: "",27 status: (28 <span>29 <p>30 {" "}31 🦊 <a target="_blank" href={`https://metamask.io/download`}>32 You must install MetaMask, a virtual Ethereum wallet, in your33 browser.34 </a>35 </p>36 </span>37 ),38 }39 }40}إظهار الكلهذا الكود مشابه جدًا لدالة connectWallet التي كتبناها للتو في وقت سابق.
الفرق الرئيسي هو أنه بدلاً من استدعاء الطريقة eth_requestAccounts، والتي تفتح MetaMask للمستخدم لربط محفظته، هنا نستدعي الطريقة eth_accounts، والتي تعيد ببساطة مصفوفة تحتوي على عناوين MetaMask المتصلة حاليًا بتطبيقنا اللامركزي.
لرؤية هذه الدالة قيد العمل، دعونا نستدعيها في دالة useEffect لمكون Minter.js الخاص بنا.
كما فعلنا مع connectWallet، يجب علينا استيراد هذه الدالة من ملف interact.js الخاص بنا إلى ملف Minter.js الخاص بنا هكذا:
1import { useEffect, useState } from "react"2import {3 connectWallet,4 getCurrentWalletConnected, //import here // الاستيراد هنا5} from "./utils/interact.js"الآن، نقوم ببساطة باستدعائها في دالة useEffect الخاصة بنا:
1useEffect(async () => {2 const { address, status } = await getCurrentWalletConnected()3 setWallet(address)4 setStatus(status)5}, [])لاحظ، أننا نستخدم استجابة استدعائنا لـ getCurrentWalletConnected لتحديث متغيرات الحالة walletAddress و status الخاصة بنا.
بمجرد إضافة هذا الكود، حاول تحديث نافذة المتصفح. يجب أن يقول الزر أنك متصل، ويعرض معاينة لعنوان محفظتك المتصلة - حتى بعد التحديث!
تنفيذ addWalletListener
الخطوة الأخيرة في إعداد محفظة التطبيق اللامركزي الخاص بنا هي تنفيذ مستمع المحفظة (wallet listener) بحيث يتم تحديث واجهة المستخدم الخاصة بنا عندما تتغير حالة محفظتنا، مثل عندما يقوم المستخدم بقطع الاتصال أو تبديل الحسابات.
في ملف Minter.js الخاص بك، أضف دالة addWalletListener تبدو كالتالي:
1function addWalletListener() {2 if (window.ethereum) {3 window.ethereum.on("accountsChanged", (accounts) => {4 if (accounts.length > 0) {5 setWallet(accounts[0])6 setStatus("👆🏽 Write a message in the text-field above.")7 } else {8 setWallet("")9 setStatus("🦊 Connect to MetaMask using the top right button.")10 }11 })12 } else {13 setStatus(14 <p>15 {" "}16 🦊 <a target="_blank" href={`https://metamask.io/download`}>17 You must install MetaMask, a virtual Ethereum wallet, in your browser.18 </a>19 </p>20 )21 }22}إظهار الكلدعونا نفصل بسرعة ما يحدث هنا:
- أولاً، تتحقق دالتنا مما إذا كان
window.ethereumممكّنًا (أي أن MetaMask مثبت).- إذا لم يكن كذلك، نقوم ببساطة بتعيين متغير الحالة
statusالخاص بنا إلى سلسلة JSX تطالب المستخدم بتثبيت MetaMask. - إذا كان ممكّنًا، نقوم بإعداد المستمع
window.ethereum.on("accountsChanged")في السطر 3 والذي يستمع لتغييرات الحالة في محفظة MetaMask، والتي تشمل عندما يقوم المستخدم بربط حساب إضافي بالتطبيق اللامركزي، أو تبديل الحسابات، أو قطع اتصال حساب. إذا كان هناك حساب واحد على الأقل متصل، يتم تحديث متغير الحالةwalletAddressكأول حساب في مصفوفةaccountsالتي يعيدها المستمع. بخلاف ذلك، يتم تعيينwalletAddressكسلسلة نصية فارغة.
- إذا لم يكن كذلك، نقوم ببساطة بتعيين متغير الحالة
أخيرًا، يجب علينا استدعاؤها في دالة useEffect الخاصة بنا:
1useEffect(async () => {2 const { address, status } = await getCurrentWalletConnected()3 setWallet(address)4 setStatus(status)56 addWalletListener()7}, [])وها نحن ذا! لقد أكملنا برمجة جميع وظائف محفظتنا! الآن بعد إعداد محفظتنا، دعونا نكتشف كيفية سك رمز NFT الخاص بنا!
أساسيات البيانات الوصفية لـ NFT
لذا تذكر البيانات الوصفية لـ NFT التي تحدثنا عنها للتو في الخطوة 0 من هذا الدليل التعليمي—إنها تجعل رمز NFT حيًا، مما يسمح له بامتلاك خصائص، مثل أصل رقمي، واسم، ووصف، وسمات أخرى.
سنحتاج إلى تكوين هذه البيانات الوصفية ككائن JSON وتخزينها، حتى نتمكن من تمريرها كمعلمة tokenURI عند استدعاء دالة mintNFT الخاصة بعقدنا الذكي.
سيتألف النص الموجود في حقول "Link to Asset" و "Name" و "Description" من الخصائص المختلفة للبيانات الوصفية لرمز NFT الخاص بنا. سنقوم بتنسيق هذه البيانات الوصفية ككائن JSON، ولكن هناك خياران لمكان تخزين كائن JSON هذا:
- يمكننا تخزينه على البلوك تشين الخاص بإيثريوم؛ ومع ذلك، فإن القيام بذلك سيكون مكلفًا للغاية.
- يمكننا تخزينه على خادم مركزي، مثل AWS أو Firebase. لكن ذلك سيتعارض مع روح اللامركزية لدينا.
- يمكننا استخدام IPFS، وهو بروتوكول لامركزي وشبكة نظير إلى نظير (peer-to-peer) لتخزين ومشاركة البيانات في نظام ملفات موزع. نظرًا لأن هذا البروتوكول لامركزي ومجاني، فهو خيارنا الأفضل!
لتخزين بياناتنا الوصفية على IPFS، سنستخدم Pinata (opens in a new tab)، وهي واجهة برمجة تطبيقات (API) ومجموعة أدوات IPFS مريحة. في الخطوة التالية، سنشرح بالضبط كيفية القيام بذلك!
استخدام Pinata لتثبيت بياناتك الوصفية على IPFS
إذا لم يكن لديك حساب Pinata (opens in a new tab)، فقم بالتسجيل للحصول على حساب مجاني هنا (opens in a new tab) وأكمل الخطوات للتحقق من بريدك الإلكتروني وحسابك.
إنشاء مفتاح API الخاص بـ Pinata
انتقل إلى صفحة https://pinata.cloud/keys (opens in a new tab)، ثم حدد زر "New Key" في الأعلى، وقم بتعيين أداة المسؤول (Admin widget) كممكّنة، وقم بتسمية مفتاحك.
ستظهر لك بعد ذلك نافذة منبثقة تحتوي على معلومات API الخاصة بك. تأكد من وضع هذا في مكان آمن.
الآن بعد إعداد مفتاحنا، دعونا نضيفه إلى مشروعنا حتى نتمكن من استخدامه.
إنشاء ملف .env
يمكننا تخزين مفتاح Pinata والسر الخاص بنا بأمان في ملف بيئة. دعونا نثبت حزمة dotenv (opens in a new tab) في دليل مشروعك.
افتح علامة تبويب جديدة في الطرفية الخاصة بك (منفصلة عن تلك التي تقوم بتشغيل المضيف المحلي) وتأكد من أنك في مجلد minter-starter-files، ثم قم بتشغيل الأمر التالي في الطرفية:
1npm install dotenv --saveبعد ذلك، قم بإنشاء ملف .env في الدليل الجذري لـ minter-starter-files عن طريق إدخال ما يلي في سطر الأوامر الخاص بك:
1vim.envسيؤدي هذا إلى فتح ملف .env الخاص بك في vim (محرر نصوص). لحفظه، اضغط على "esc" + ":" + "q" على لوحة المفاتيح بهذا الترتيب.
بعد ذلك، في VSCode، انتقل إلى ملف .env الخاص بك وأضف مفتاح API الخاص بـ Pinata وسر API إليه، هكذا:
1REACT_APP_PINATA_KEY = <pinata-api-key>2REACT_APP_PINATA_SECRET = <pinata-api-secret>احفظ الملف، وبعد ذلك ستكون جاهزًا للبدء في كتابة الدالة لتحميل بيانات JSON الوصفية الخاصة بك إلى IPFS!
تنفيذ pinJSONToIPFS
لحسن الحظ بالنسبة لنا، تمتلك Pinata واجهة برمجة تطبيقات (API) مخصصة لتحميل بيانات JSON إلى IPFS (opens in a new tab) ومثال JavaScript مريح مع axios يمكننا استخدامه، مع بعض التعديلات الطفيفة.
في مجلد utils الخاص بك، دعونا ننشئ ملفًا آخر يسمى pinata.js ثم نستورد سر ومفتاح Pinata الخاصين بنا من ملف .env هكذا:
1require("dotenv").config()2const key = process.env.REACT_APP_PINATA_KEY3const secret = process.env.REACT_APP_PINATA_SECRETبعد ذلك، الصق الكود الإضافي من الأسفل في ملف pinata.js الخاص بك. لا تقلق، سنفصل ما يعنيه كل شيء!
1require("dotenv").config()2const key = process.env.REACT_APP_PINATA_KEY3const secret = process.env.REACT_APP_PINATA_SECRET45const axios = require("axios")67export const pinJSONToIPFS = async (JSONBody) => {8 const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`9 //making axios POST request to Pinata ⬇️ // إجراء طلب POST عبر axios إلى Pinata ⬇️10 return axios11 .post(url, JSONBody, {12 headers: {13 pinata_api_key: key,14 pinata_secret_api_key: secret,15 },16 })17 .then(function (response) {18 return {19 success: true,20 pinataUrl:21 "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,22 }23 })24 .catch(function (error) {25 console.log(error)26 return {27 success: false,28 message: error.message,29 }30 })31}إظهار الكلإذن ماذا يفعل هذا الكود بالضبط؟
أولاً، يقوم باستيراد axios (opens in a new tab)، وهو عميل HTTP قائم على الوعود (promise based) للمتصفح و node.js، والذي سنستخدمه لإجراء طلب إلى Pinata.
ثم لدينا دالتنا غير المتزامنة pinJSONToIPFS، والتي تأخذ JSONBody كمدخل لها ومفتاح وسر Pinata api في ترويستها (header)، كل ذلك لإجراء طلب POST إلى واجهة برمجة تطبيقات pinJSONToIPFS الخاصة بهم.
- إذا نجح طلب POST هذا، فإن دالتنا تعيد كائن JSON مع القيمة المنطقية
successكـ true وpinataUrlحيث تم تثبيت بياناتنا الوصفية. سنستخدمpinataUrlالمُرجع هذا كمدخلtokenURIلدالة السك الخاصة بعقدنا الذكي. - إذا فشل طلب POST هذا، فإن دالتنا تعيد كائن JSON مع القيمة المنطقية
successكـ false وسلسلة نصيةmessageتنقل خطأنا.
كما هو الحال مع أنواع الإرجاع لدالة connectWallet الخاصة بنا، فإننا نعيد كائنات JSON حتى نتمكن من استخدام معلماتها لتحديث متغيرات الحالة وواجهة المستخدم الخاصة بنا.
تحميل العقد الذكي الخاص بك
الآن بعد أن أصبح لدينا طريقة لتحميل البيانات الوصفية لـ NFT الخاصة بنا إلى IPFS عبر دالة pinJSONToIPFS الخاصة بنا، سنحتاج إلى طريقة لتحميل مثيل (instance) لعقدنا الذكي حتى نتمكن من استدعاء دالة mintNFT الخاصة به.
كما ذكرنا سابقًا، في هذا الدليل التعليمي سنستخدم هذا العقد الذكي الموجود لـ NFT (opens in a new tab)؛ ومع ذلك، إذا كنت ترغب في معرفة كيف قمنا بإنشائه، أو إنشاء واحد بنفسك، نوصي بشدة بالاطلاع على دليلنا التعليمي الآخر، "كيفية إنشاء NFT." (opens in a new tab).
واجهة التطبيق الثنائية (ABI) للعقد
إذا قمت بفحص ملفاتنا عن كثب، ستلاحظ أنه في دليل src الخاص بنا، يوجد ملف contract-abi.json. تعد واجهة التطبيق الثنائية (ABI) ضرورية لتحديد الدالة التي سيستدعيها العقد بالإضافة إلى ضمان أن الدالة ستعيد البيانات بالتنسيق الذي تتوقعه.
سنحتاج أيضًا إلى مفتاح Alchemy API و Alchemy Web3 API للاتصال بالبلوك تشين الخاص بإيثريوم وتحميل عقدنا الذكي.
إنشاء مفتاح Alchemy API الخاص بك
إذا لم يكن لديك حساب Alchemy بالفعل، قم بالتسجيل مجانًا هنا. (opens in a new tab)
بمجرد إنشاء حساب Alchemy، يمكنك إنشاء مفتاح API عن طريق إنشاء تطبيق. سيسمح لنا هذا بإجراء طلبات إلى شبكة الاختبار Ropsten.
انتقل إلى صفحة "Create App" في لوحة تحكم Alchemy الخاصة بك عن طريق التمرير فوق "Apps" في شريط التنقل والنقر على "Create App".
قم بتسمية تطبيقك، لقد اخترنا "My First NFT!"، وقدم وصفًا قصيرًا، وحدد "Staging" للبيئة المستخدمة لمسك دفاتر تطبيقك، واختر "Ropsten" لشبكتك.
انقر على "Create app" وهذا كل شيء! يجب أن يظهر تطبيقك في الجدول أدناه.
رائع، الآن بعد أن أنشأنا عنوان URL الخاص بـ HTTP Alchemy API، انسخه إلى الحافظة الخاصة بك...
…ثم دعونا نضيفه إلى ملف .env الخاص بنا. إجمالاً، يجب أن يبدو ملف .env الخاص بك هكذا:
1REACT_APP_PINATA_KEY = <pinata-key>2REACT_APP_PINATA_SECRET = <pinata-secret>3REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/<alchemy-key> // eth-ropsten.alchemyapi.io/v2/<alchemy-key>الآن بعد أن أصبح لدينا ABI الخاص بعقدنا ومفتاح Alchemy API الخاص بنا، نحن مستعدون لتحميل عقدنا الذكي باستخدام Alchemy Web3 (opens in a new tab).
إعداد نقطة نهاية Alchemy Web3 والعقد الخاص بك
أولاً، إذا لم يكن لديك بالفعل، فستحتاج إلى تثبيت Alchemy Web3 (opens in a new tab) عن طريق الانتقال إلى الدليل الرئيسي: nft-minter-tutorial في الطرفية:
1cd ..2npm install @alch/alchemy-web3بعد ذلك دعونا نعود إلى ملف interact.js الخاص بنا. في الجزء العلوي من الملف، أضف الكود التالي لاستيراد مفتاح Alchemy الخاص بك من ملف .env الخاص بك وإعداد نقطة نهاية Alchemy Web3 الخاصة بك:
1require("dotenv").config()2const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")4const web3 = createAlchemyWeb3(alchemyKey)Alchemy Web3 (opens in a new tab) هو غلاف (wrapper) حول Web3.js (opens in a new tab)، يوفر طرق API محسنة وفوائد حاسمة أخرى لجعل حياتك كمطور web3 أسهل. تم تصميمه ليتطلب الحد الأدنى من التكوين حتى تتمكن من البدء في استخدامه في تطبيقك على الفور!
بعد ذلك، دعونا نضيف ABI الخاص بعقدنا وعنوان العقد إلى ملفنا.
1require("dotenv").config()2const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")4const web3 = createAlchemyWeb3(alchemyKey)56const contractABI = require("../contract-abi.json")7const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"بمجرد أن نحصل على كليهما، نكون مستعدين للبدء في كتابة كود دالة السك الخاصة بنا!
تنفيذ الدالة mintNFT
داخل ملف interact.js الخاص بك، دعونا نحدد دالتنا، mintNFT، والتي ستقوم بسك رمز NFT الخاص بنا كما يوحي اسمها.
لأننا سنجري العديد من الاستدعاءات غير المتزامنة (إلى Pinata لتثبيت بياناتنا الوصفية على IPFS، و Alchemy Web3 لتحميل عقدنا الذكي، و MetaMask لتوقيع معاملاتنا)، ستكون دالتنا أيضًا غير متزامنة.
المدخلات الثلاثة لدالتنا ستكون url الخاص بأصلنا الرقمي، و name، و description. أضف توقيع الدالة التالي أسفل دالة connectWallet:
1export const mintNFT = async (url, name, description) => {}معالجة أخطاء الإدخال
بطبيعة الحال، من المنطقي أن يكون لدينا نوع من معالجة أخطاء الإدخال في بداية الدالة، لذلك نخرج من هذه الدالة إذا لم تكن معلمات الإدخال الخاصة بنا صحيحة. داخل دالتنا، دعونا نضيف الكود التالي:
1export const mintNFT = async (url, name, description) => {2 //error handling // معالجة الأخطاء3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {4 return {5 success: false,6 status: "❗Please make sure all fields are completed before minting.",7 }8 }9}إظهار الكلبشكل أساسي، إذا كان أي من معلمات الإدخال عبارة عن سلسلة نصية فارغة، فإننا نعيد كائن JSON حيث تكون القيمة المنطقية success هي false، وتنقل السلسلة النصية status أنه يجب إكمال جميع الحقول في واجهة المستخدم الخاصة بنا.
تحميل البيانات الوصفية إلى IPFS
بمجرد أن نعرف أن بياناتنا الوصفية منسقة بشكل صحيح، فإن الخطوة التالية هي تغليفها في كائن JSON وتحميلها إلى IPFS عبر pinJSONToIPFS التي كتبناها!
للقيام بذلك، نحتاج أولاً إلى استيراد دالة pinJSONToIPFS إلى ملف interact.js الخاص بنا. في الجزء العلوي من interact.js، دعونا نضيف:
1import { pinJSONToIPFS } from "./pinata.js"تذكر أن pinJSONToIPFS تأخذ جسم JSON. لذا قبل أن نجري استدعاءً لها، سنحتاج إلى تنسيق معلمات url، و name، و description الخاصة بنا في كائن JSON.
دعونا نحدث الكود الخاص بنا لإنشاء كائن JSON يسمى metadata ثم نجري استدعاءً لـ pinJSONToIPFS باستخدام معلمة metadata هذه:
1export const mintNFT = async (url, name, description) => {2 //error handling // معالجة الأخطاء3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {4 return {5 success: false,6 status: "❗Please make sure all fields are completed before minting.",7 }8 }910 //make metadata // إنشاء البيانات الوصفية11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //make pinata call // إجراء طلب Pinata17 const pinataResponse = await pinJSONToIPFS(metadata)18 if (!pinataResponse.success) {19 return {20 success: false,21 status: "😢 Something went wrong while uploading your tokenURI.",22 }23 }24 const tokenURI = pinataResponse.pinataUrl25}إظهار الكللاحظ، أننا نخزن استجابة استدعائنا لـ pinJSONToIPFS(metadata) في كائن pinataResponse. ثم، نقوم بتحليل هذا الكائن بحثًا عن أي أخطاء.
إذا كان هناك خطأ، فإننا نعيد كائن JSON حيث تكون القيمة المنطقية success هي false وتنقل السلسلة النصية status الخاصة بنا أن استدعاءنا فشل. بخلاف ذلك، نستخرج pinataURL من pinataResponse ونخزنه كمتغير tokenURI الخاص بنا.
الآن حان الوقت لتحميل عقدنا الذكي باستخدام Alchemy Web3 API الذي قمنا بتهيئته في الجزء العلوي من ملفنا. أضف سطر الكود التالي إلى أسفل دالة mintNFT لتعيين العقد في المتغير العالمي window.contract:
1window.contract = await new web3.eth.Contract(contractABI, contractAddress)آخر شيء يجب إضافته في دالة mintNFT الخاصة بنا هو معاملة إيثريوم الخاصة بنا:
1//set up your Ethereum transaction // إعداد معاملة إيثريوم الخاصة بك2const transactionParameters = {3 to: contractAddress, // Required except during contract publications. // مطلوب باستثناء أوقات نشر العقد.4 from: window.ethereum.selectedAddress, // must match user's active address. // يجب أن يتطابق مع عنوان المستخدم النشط.5 data: window.contract.methods6 .mintNFT(window.ethereum.selectedAddress, tokenURI)7 .encodeABI(), //make call to NFT smart contract // إجراء استدعاء للعقد الذكي لـ NFT8}910//sign the transaction via MetaMask // توقيع المعاملة عبر MetaMask11try {12 const txHash = await window.ethereum.request({13 method: "eth_sendTransaction",14 params: [transactionParameters],15 })16 return {17 success: true,18 status:19 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +20 txHash,21 }22} catch (error) {23 return {24 success: false,25 status: "😥 Something went wrong: " + error.message,26 }27}إظهار الكلإذا كنت معتادًا بالفعل على معاملات إيثريوم، فستلاحظ أن الهيكل مشابه جدًا لما رأيته.
- أولاً، نقوم بإعداد معلمات المعاملات الخاصة بنا.
toيحدد عنوان المستلم (عقدنا الذكي)fromيحدد موقع المعاملة (عنوان المستخدم المتصل بـ MetaMask:window.ethereum.selectedAddress)dataيحتوي على استدعاء لطريقةmintNFTالخاصة بعقدنا الذكي، والتي تتلقىtokenURIالخاص بنا وعنوان محفظة المستخدم،window.ethereum.selectedAddress، كمدخلات
- ثم، نجري استدعاء await،
window.ethereum.request، حيث نطلب من MetaMask توقيع المعاملة. لاحظ، في هذا الطلب، أننا نحدد طريقة eth الخاصة بنا (eth_SentTransaction) ونمررtransactionParametersالخاصة بنا. في هذه المرحلة، سيتم فتح MetaMask في المتصفح، ويطالب المستخدم بتوقيع المعاملة أو رفضها.- إذا نجحت المعاملة، ستعيد الدالة كائن JSON حيث يتم تعيين القيمة المنطقية
successإلى true وتطالب السلسلة النصيةstatusالمستخدم بالتحقق من Etherscan لمزيد من المعلومات حول معاملته. - إذا فشلت المعاملة، ستعيد الدالة كائن JSON حيث يتم تعيين القيمة المنطقية
successإلى false، وتنقل السلسلة النصيةstatusرسالة الخطأ.
- إذا نجحت المعاملة، ستعيد الدالة كائن JSON حيث يتم تعيين القيمة المنطقية
إجمالاً، يجب أن تبدو دالة mintNFT الخاصة بنا هكذا:
1export const mintNFT = async (url, name, description) => {2 //error handling // معالجة الأخطاء3 if (url.trim() == "" || name.trim() == "" || description.trim() == "") {4 return {5 success: false,6 status: "❗Please make sure all fields are completed before minting.",7 }8 }910 //make metadata // إنشاء البيانات الوصفية11 const metadata = new Object()12 metadata.name = name13 metadata.image = url14 metadata.description = description1516 //pinata pin request // طلب تثبيت Pinata17 const pinataResponse = await pinJSONToIPFS(metadata)18 if (!pinataResponse.success) {19 return {20 success: false,21 status: "😢 Something went wrong while uploading your tokenURI.",22 }23 }24 const tokenURI = pinataResponse.pinataUrl2526 //load smart contract // تحميل العقد الذكي27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract(); // loadContract();2829 //set up your Ethereum transaction // إعداد معاملة إيثريوم الخاصة بك30 const transactionParameters = {31 to: contractAddress, // Required except during contract publications. // مطلوب باستثناء أوقات نشر العقد.32 from: window.ethereum.selectedAddress, // must match user's active address. // يجب أن يتطابق مع عنوان المستخدم النشط.33 data: window.contract.methods34 .mintNFT(window.ethereum.selectedAddress, tokenURI)35 .encodeABI(), //make call to NFT smart contract // إجراء استدعاء للعقد الذكي لـ NFT36 }3738 //sign transaction via MetaMask // توقيع المعاملة عبر MetaMask39 try {40 const txHash = await window.ethereum.request({41 method: "eth_sendTransaction",42 params: [transactionParameters],43 })44 return {45 success: true,46 status:47 "✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +48 txHash,49 }50 } catch (error) {51 return {52 success: false,53 status: "😥 Something went wrong: " + error.message,54 }55 }56}إظهار الكلهذه دالة عملاقة واحدة! الآن، نحتاج فقط إلى ربط دالة mintNFT الخاصة بنا بمكون Minter.js الخاص بنا...
ربط mintNFT بالواجهة الأمامية Minter.js الخاصة بنا
افتح ملف Minter.js الخاص بك وقم بتحديث سطر import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; في الأعلى ليكون:
1import {2 connectWallet,3 getCurrentWalletConnected,4 mintNFT,5} from "./utils/interact.js"أخيرًا، قم بتنفيذ دالة onMintPressed لإجراء استدعاء await لدالة mintNFT المستوردة وتحديث متغير الحالة status ليعكس ما إذا كانت معاملتنا قد نجحت أو فشلت:
1const onMintPressed = async () => {2 const { status } = await mintNFT(url, name, description)3 setStatus(status)4}نشر NFT الخاص بك على موقع ويب مباشر
هل أنت مستعد لجعل مشروعك مباشرًا ليتفاعل معه المستخدمون؟ تحقق من هذا الدليل التعليمي (opens in a new tab) لنشر أداة السك (Minter) الخاصة بك على موقع ويب مباشر.
خطوة أخيرة...
اكتساح عالم البلوك تشين
أمزح فقط، لقد وصلت إلى نهاية الدليل التعليمي!
للتلخيص، من خلال بناء أداة سك NFT، لقد تعلمت بنجاح كيفية:
- الاتصال بـ MetaMask عبر مشروع الواجهة الأمامية الخاص بك
- استدعاء دوال العقد الذكي من الواجهة الأمامية
- توقيع المعاملات باستخدام MetaMask
من المفترض أنك ترغب في أن تكون قادرًا على التباهي برموز NFT التي تم سكها عبر تطبيقك اللامركزي في محفظتك — لذا تأكد من الاطلاع على دليلنا التعليمي السريع كيفية عرض NFT الخاص بك في محفظتك (opens in a new tab)!
وكما هو الحال دائمًا، إذا كان لديك أي أسئلة، فنحن هنا للمساعدة في Alchemy Discord (opens in a new tab). لا يسعنا الانتظار لرؤية كيف تطبق المفاهيم من هذا الدليل التعليمي على مشاريعك المستقبلية!
آخر تحديث للصفحة: 25 فبراير 2026