برنامج تعليمي لسك NFT
أحد أكبر التحديات التي تواجه المطورين القادمين من خلفية ويب 2 هو معرفة كيفية ربط عقدك الذكي بمشروع واجهة أمامية والتفاعل معه.
من خلال بناء أداة لسك NFT — وهي واجهة مستخدم بسيطة حيث يمكنك إدخال رابط إلى أصل رقمي خاص بك، وعنوان، ووصف — ستتعلم كيفية:
- الاتصال بمحفظة ميتاماسك عبر مشروع الواجهة الأمامية الخاص بك
- استدعاء وظائف العقد الذكي من واجهتك الأمامية
- توقيع المعاملات باستخدام ميتاماسك
في هذا البرنامج التعليمي، سنستخدم React (opens in a new tab) كإطار عمل للواجهة الأمامية. نظرًا لأن هذا البرنامج التعليمي يركز بشكل أساسي على تطوير Web3، فلن نقضي الكثير من الوقت في تفصيل أساسيات React. بدلاً من ذلك، سنركز على إضافة الوظائف إلى مشروعنا.
كمتطلب أساسي، يجب أن يكون لديك فهم بمستوى المبتدئين لـ React—معرفة كيفية عمل المكونات (components)، والخصائص (props)، وuseState/useEffect، واستدعاء الوظائف الأساسية. إذا لم تسمع بأي من هذه المصطلحات من قبل، فقد ترغب في الاطلاع على البرنامج التعليمي لمقدمة React (opens in a new tab). بالنسبة للمتعلمين البصريين، نوصي بشدة بسلسلة مقاطع الفيديو الممتازة البرنامج التعليمي الشامل لـ React الحديث (opens in a new tab) بواسطة Net Ninja.
وإذا لم تكن قد فعلت ذلك بالفعل، فستحتاج بالتأكيد إلى حساب Alchemy لإكمال هذا البرنامج التعليمي بالإضافة إلى بناء أي شيء على سلسلة الكتل. قم بالتسجيل للحصول على حساب مجاني هنا (opens in a new tab).
بدون مزيد من التأخير، دعونا نبدأ!
أساسيات صنع NFTs
قبل أن نبدأ حتى في النظر إلى أي كود، من المهم أن نفهم كيف تعمل عملية صنع NFT. إنها تتضمن خطوتين:
نشر عقد ذكي لـ NFT على سلسلة الكتل لإيثيريوم
أكبر اختلاف بين معياري العقود الذكية لـ NFT هو أن ERC-1155 هو معيار متعدد الرموز ويتضمن وظيفة الدفعات، بينما ERC-721 هو معيار لرمز مميز واحد وبالتالي يدعم فقط نقل رمز مميز واحد في كل مرة.
استدعاء وظيفة السك
عادةً، تتطلب وظيفة السك هذه تمرير متغيرين كمعلمات، الأول هو recipient، والذي يحدد العنوان الذي سيتلقى NFT المسكوك حديثًا، والثاني هو tokenURI الخاص بـ NFT، وهو سلسلة نصية تشير إلى مستند JSON يصف البيانات الوصفية لـ 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-files
npm 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 الأساسي الذي سنعمل عليه.
في الجزء العلوي من هذا الملف، لدينا متغيرات الحالة التي سنقوم بتحديثها بعد أحداث معينة.
//متغيرات الحالة
const [walletAddress, setWallet] = useState("")
const [status, setStatus] = useState("")
const [name, setName] = useState("")
const [description, setDescription] = useState("")
const [url, setURL] = useState("")
لم تسمع من قبل عن متغيرات حالة React أو خطافات الحالة (state hooks)؟ تحقق من هذه (opens in a new tab) المستندات.
إليك ما يمثله كل من المتغيرات:
walletAddress- سلسلة نصية تخزن عنوان محفظة المستخدمstatus- سلسلة نصية تحتوي على رسالة لعرضها في أسفل واجهة المستخدمname- سلسلة نصية تخزن اسم NFTdescription- سلسلة نصية تخزن وصف NFTurl- سلسلة نصية تمثل رابطًا للأصل الرقمي لـ NFT
بعد متغيرات الحالة، سترى ثلاث وظائف غير منفذة: useEffect، و connectWalletPressed، و onMintPressed. ستلاحظ أن جميع هذه الوظائف هي async، وذلك لأننا سنجري استدعاءات API غير متزامنة بداخلها! أسماؤها تعكس وظائفها:
useEffect(async () => {
//TODO: التنفيذ
}, [])
const connectWalletPressed = async () => {
//TODO: التنفيذ
}
const onMintPressed = async () => {
//TODO: التنفيذ
}
useEffect(opens in a new tab) - هذا خطاف React يتم استدعاؤه بعد تصيير (render) المكون الخاص بك. نظرًا لأنه يتم تمرير خاصية مصفوفة فارغة[]إليه (انظر السطر 3)، فسيتم استدعاؤه فقط في التصيير الأول للمكون. هنا سنستدعي مستمع المحفظة الخاص بنا ووظيفة محفظة أخرى لتحديث واجهة المستخدم الخاصة بنا لتعكس ما إذا كانت المحفظة متصلة بالفعل.connectWalletPressed- سيتم استدعاء هذه الوظيفة لربط محفظة ميتاماسك الخاصة بالمستخدم بتطبيقنا اللامركزي (dapp).onMintPressed- سيتم استدعاء هذه الوظيفة لسك NFT الخاص بالمستخدم.
بالقرب من نهاية هذا الملف، لدينا واجهة المستخدم الخاصة بمكوننا. إذا قمت بفحص هذا الكود بعناية، ستلاحظ أننا نقوم بتحديث متغيرات الحالة url، و name، و description عندما يتغير الإدخال في حقول النص المقابلة لها.
سترى أيضًا أنه يتم استدعاء connectWalletPressed و onMintPressed عند النقر على الأزرار ذات المعرفات mintButton و walletButton على التوالي.
//واجهة المستخدم للمكون الخاص بنا
return (
<div className="Minter">
<button id="walletButton" onClick={connectWalletPressed}>
{walletAddress.length > 0 ? (
"Connected: " +
String(walletAddress).substring(0, 6) +
"..." +
String(walletAddress).substring(38)
) : (
<span>Connect Wallet</span>
)}
</button>
<br></br>
<h1 id="title">🧙♂️ Alchemy NFT Minter</h1>
<p>
Simply add your asset's link, name, and description, then press "Mint."
</p>
<form>
<h2>🖼 Link to asset: </h2>
<input
type="text"
placeholder="e.g., https://gateway.pinata.cloud/ipfs/<hash>"
onChange={(event) => setURL(event.target.value)}
/>
<h2>🤔 Name: </h2>
<input
type="text"
placeholder="e.g., My first NFT!"
onChange={(event) => setName(event.target.value)}
/>
<h2>✍️ Description: </h2>
<input
type="text"
placeholder="e.g., Even cooler than cryptokitties ;)"
onChange={(event) => setDescription(event.target.value)}
/>
</form>
<button id="mintButton" onClick={onMintPressed}>
Mint NFT
</button>
<p id="status">{status}</p>
</div>
)
أخيرًا، دعونا نتناول أين يتم إضافة مكون Minter هذا.
إذا ذهبت إلى ملف App.js، وهو المكون الرئيسي في React الذي يعمل كحاوية لجميع المكونات الأخرى، سترى أن مكون Minter الخاص بنا يتم حقنه في السطر 7.
في هذا البرنامج التعليمي، سنقوم فقط بتعديل Minter.js file وإضافة ملفات في مجلد src الخاص بنا.
الآن بعد أن فهمنا ما نعمل عليه، دعونا نعد محفظة إيثيريوم الخاصة بنا!
إعداد محفظة إيثيريوم الخاصة بك
لكي يتمكن المستخدمون من التفاعل مع عقدك الذكي، سيحتاجون إلى ربط محفظة إيثيريوم الخاصة بهم بتطبيقك اللامركزي (dapp).
تنزيل ميتاماسك
في هذا البرنامج التعليمي، سنستخدم ميتاماسك، وهي محفظة افتراضية في المتصفح تُستخدم لإدارة عنوان حساب إيثيريوم الخاص بك. إذا كنت ترغب في فهم المزيد حول كيفية عمل المعاملات على إيثيريوم، تحقق من هذه الصفحة.
يمكنك تنزيل وإنشاء حساب ميتاماسك مجانًا هنا (opens in a new tab). عند إنشاء حساب، أو إذا كان لديك حساب بالفعل، تأكد من التبديل إلى "Ropsten Test Network" في الزاوية العلوية اليمنى (حتى لا نتعامل بأموال حقيقية).
إضافة إيثر من صنبور
من أجل سك NFTs الخاصة بنا (أو توقيع أي معاملات على سلسلة الكتل لإيثيريوم)، سنحتاج إلى بعض الـ ETH الوهمي. للحصول على ETH، يمكنك الذهاب إلى صنبور روبستن (opens in a new tab) وإدخال عنوان حساب روبستن الخاص بك، ثم النقر على "Send Ropsten Eth". يجب أن ترى ETH في حساب ميتاماسك الخاص بك بعد فترة وجيزة!
التحقق من رصيدك
للتأكد من وجود رصيدنا، دعونا نجري طلب eth_getBalance (opens in a new tab) باستخدام أداة الملحن الخاصة بـ Alchemy (opens in a new tab). سيعيد هذا مقدار ETH في محفظتنا. بعد إدخال عنوان حساب ميتاماسك الخاص بك والنقر على "Send Request"، يجب أن ترى استجابة مثل هذه:
{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}
ملاحظة: هذه النتيجة بوحدة Wei وليس ETH. تُستخدم Wei كأصغر فئة من الإيثر. التحويل من Wei إلى ETH هو: 1 eth = 10¹⁸ wei. لذا إذا قمنا بتحويل 0xde0b6b3a7640000 إلى النظام العشري نحصل على 1*10¹⁸ والذي يساوي 1 eth.
يا للعجب! أموالنا الوهمية كلها موجودة!
ربط ميتاماسك بواجهة المستخدم الخاصة بك
الآن بعد إعداد محفظة ميتاماسك الخاصة بنا، دعونا نربط تطبيقنا اللامركزي (dapp) بها!
لأننا نريد الالتزام بنموذج MVC (opens in a new tab)، سنقوم بإنشاء ملف منفصل يحتوي على وظائفنا لإدارة المنطق، والبيانات، والقواعد الخاصة بتطبيقنا اللامركزي (dapp)، ثم تمرير تلك الوظائف إلى واجهتنا الأمامية (مكون Minter.js الخاص بنا).
وظيفة connectWallet
للقيام بذلك، دعونا ننشئ مجلدًا جديدًا يسمى utils في دليل src الخاص بك ونضيف ملفًا يسمى interact.js بداخله، والذي سيحتوي على جميع وظائف التفاعل مع المحفظة والعقد الذكي.
في ملف interact.js الخاص بنا، سنكتب وظيفة connectWallet، والتي سنقوم بعد ذلك باستيرادها واستدعائها في مكون Minter.js الخاص بنا.
في ملف interact.js الخاص بك، أضف ما يلي
export const connectWallet = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_requestAccounts",
})
const obj = {
status: "👆🏽 Write a message in the text-field above.",
address: addressArray[0],
}
return obj
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
دعونا نفصل ما يفعله هذا الكود:
أولاً، تتحقق وظيفتنا مما إذا كان window.ethereum ممكّنًا في متصفحك.
window.ethereum هو API عالمي يتم حقنه بواسطة ميتاماسك ومزودي المحافظ الآخرين والذي يسمح لمواقع الويب بطلب حسابات إيثيريوم الخاصة بالمستخدمين. إذا تمت الموافقة، يمكنه قراءة البيانات من سلاسل الكتل التي يتصل بها المستخدم، واقتراح أن يقوم المستخدم بتوقيع الرسائل والمعاملات. تحقق من مستندات ميتاماسك (opens in a new tab) لمزيد من المعلومات!
إذا كان window.ethereum غير موجود، فهذا يعني أن ميتاماسك غير مثبت. يؤدي هذا إلى إرجاع كائن JSON، حيث يكون address المُرجع عبارة عن سلسلة نصية فارغة، وينقل كائن JSX status أنه يجب على المستخدم تثبيت ميتاماسك.
معظم الوظائف التي نكتبها ستعيد كائنات JSON يمكننا استخدامها لتحديث متغيرات الحالة وواجهة المستخدم الخاصة بنا.
الآن إذا كان window.ethereum موجودًا، فهنا تصبح الأمور مثيرة للاهتمام.
باستخدام حلقة try/catch، سنحاول الاتصال بميتاماسك عن طريق استدعاء window.ethereum.request({ method: "eth_requestAccounts" }); (opens in a new tab). سيؤدي استدعاء هذه الوظيفة إلى فتح ميتاماسك في المتصفح، حيث سيُطلب من المستخدم ربط محفظته بتطبيقك اللامركزي (dapp).
- إذا اختار المستخدم الاتصال، سيعيد
method: "eth_requestAccounts"مصفوفة تحتوي على جميع عناوين حسابات المستخدم المتصلة بالتطبيق اللامركزي (dapp). إجمالاً، ستعيد وظيفة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 الآن هكذا:
import { useEffect, useState } from "react";
import { connectWallet } from "./utils/interact.js";
const Minter = (props) => {
//متغيرات الحالة
const [walletAddress, setWallet] = useState("");
const [status, setStatus] = useState("");
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [url, setURL] = useState("");
ثم، داخل وظيفة connectWalletPressed الخاصة بنا، سنستدعي وظيفة connectWallet المستوردة، هكذا:
const connectWalletPressed = async () => {
const walletResponse = await connectWallet()
setStatus(walletResponse.status)
setWallet(walletResponse.address)
}
هل تلاحظ كيف يتم تجريد معظم وظائفنا بعيدًا عن مكون Minter.js الخاص بنا من ملف interact.js؟ هذا لكي نتوافق مع نموذج M-V-C!
في connectWalletPressed، نقوم ببساطة بإجراء استدعاء await لوظيفة connectWallet المستوردة، وباستخدام استجابتها، نقوم بتحديث متغيرات status و walletAddress الخاصة بنا عبر خطافات الحالة الخاصة بها.
الآن، دعونا نحفظ كلا الملفين Minter.js و interact.js ونختبر واجهة المستخدم الخاصة بنا حتى الآن.
افتح متصفحك على localhost:3000، واضغط على زر "Connect Wallet" في أعلى يمين الصفحة.
إذا كان لديك ميتاماسك مثبتًا، فسيُطلب منك ربط محفظتك بتطبيقك اللامركزي (dapp). اقبل دعوة الاتصال.
يجب أن ترى أن زر المحفظة يعكس الآن أن عنوانك متصل.
بعد ذلك، حاول تحديث الصفحة... هذا غريب. يطالبنا زر المحفظة الخاص بنا بربط ميتاماسك، على الرغم من أنه متصل بالفعل...
لا تقلق مع ذلك! يمكننا إصلاح ذلك بسهولة عن طريق تنفيذ وظيفة تسمى getCurrentWalletConnected، والتي ستتحقق مما إذا كان هناك عنوان متصل بالفعل بتطبيقنا اللامركزي (dapp) وتحديث واجهة المستخدم الخاصة بنا وفقًا لذلك!
وظيفة getCurrentWalletConnected
في ملف interact.js الخاص بك، أضف وظيفة getCurrentWalletConnected التالية:
export const getCurrentWalletConnected = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_accounts",
})
if (addressArray.length > 0) {
return {
address: addressArray[0],
status: "👆🏽 Write a message in the text-field above.",
}
} else {
return {
address: "",
status: "🦊 Connect to MetaMask using the top right button.",
}
}
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
}
}
} else {
return {
address: "",
status: (
<span>
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
}
}
}
هذا الكود مشابه جدًا لوظيفة connectWallet التي كتبناها للتو في وقت سابق.
الفرق الرئيسي هو أنه بدلاً من استدعاء الطريقة eth_requestAccounts، والتي تفتح ميتاماسك للمستخدم لربط محفظته، نستدعي هنا الطريقة eth_accounts، والتي تعيد ببساطة مصفوفة تحتوي على عناوين ميتاماسك المتصلة حاليًا بتطبيقنا اللامركزي (dapp).
لرؤية هذه الوظيفة قيد العمل، دعونا نستدعيها في وظيفة useEffect لمكون Minter.js الخاص بنا.
كما فعلنا مع connectWallet، يجب علينا استيراد هذه الوظيفة من ملف interact.js الخاص بنا إلى ملف Minter.js الخاص بنا هكذا:
import { useEffect, useState } from "react"
import {
connectWallet,
getCurrentWalletConnected, //الاستيراد هنا
} from "./utils/interact.js"
الآن، نقوم ببساطة باستدعائها في وظيفة useEffect الخاصة بنا:
useEffect(async () => {
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
}, [])
لاحظ، نستخدم استجابة استدعائنا لـ getCurrentWalletConnected لتحديث متغيرات الحالة walletAddress و status الخاصة بنا.
بمجرد إضافة هذا الكود، حاول تحديث نافذة المتصفح الخاصة بنا. يجب أن يقول الزر أنك متصل، ويعرض معاينة لعنوان محفظتك المتصلة - حتى بعد التحديث!
تنفيذ addWalletListener
الخطوة الأخيرة في إعداد محفظة التطبيق اللامركزي (dapp) الخاص بنا هي تنفيذ مستمع المحفظة بحيث يتم تحديث واجهة المستخدم الخاصة بنا عندما تتغير حالة محفظتنا، مثل عندما يقوم المستخدم بقطع الاتصال أو تبديل الحسابات.
في ملف Minter.js الخاص بك، أضف وظيفة addWalletListener تبدو كالتالي:
function addWalletListener() {
if (window.ethereum) {
window.ethereum.on("accountsChanged", (accounts) => {
if (accounts.length > 0) {
setWallet(accounts[0])
setStatus("👆🏽 Write a message in the text-field above.")
} else {
setWallet("")
setStatus("🦊 Connect to MetaMask using the top right button.")
}
})
} else {
setStatus(
<p>
{" "}
🦊 <a target="_blank" href={`https://metamask.io/download`}>
You must install MetaMask, a virtual Ethereum wallet, in your browser.
</a>
</p>
)
}
}
دعونا نفصل بسرعة ما يحدث هنا:
- أولاً، تتحقق وظيفتنا مما إذا كان
window.ethereumممكّنًا (أي أن ميتاماسك مثبت).- إذا لم يكن كذلك، نقوم ببساطة بتعيين متغير الحالة
statusالخاص بنا إلى سلسلة نصية JSX تطالب المستخدم بتثبيت ميتاماسك. - إذا كان ممكّنًا، نقوم بإعداد المستمع
window.ethereum.on("accountsChanged")في السطر 3 والذي يستمع لتغييرات الحالة في محفظة ميتاماسك، والتي تشمل عندما يقوم المستخدم بربط حساب إضافي بالتطبيق اللامركزي (dapp)، أو تبديل الحسابات، أو قطع اتصال حساب. إذا كان هناك حساب واحد على الأقل متصل، يتم تحديث متغير الحالةwalletAddressكأول حساب في مصفوفةaccountsالتي يعيدها المستمع. بخلاف ذلك، يتم تعيينwalletAddressكسلسلة نصية فارغة.
- إذا لم يكن كذلك، نقوم ببساطة بتعيين متغير الحالة
أخيرًا، يجب علينا استدعاؤها في وظيفة useEffect الخاصة بنا:
useEffect(async () => {
const { address, status } = await getCurrentWalletConnected()
setWallet(address)
setStatus(status)
addWalletListener()
}, [])
وها نحن ذا! لقد أكملنا برمجة جميع وظائف محفظتنا! الآن بعد إعداد محفظتنا، دعونا نكتشف كيفية سك NFT الخاص بنا!
أساسيات البيانات الوصفية لـ NFT
لذا تذكر البيانات الوصفية لـ NFT التي تحدثنا عنها للتو في الخطوة 0 من هذا البرنامج التعليمي—إنها تبعث الحياة في NFT، مما يسمح له بامتلاك خصائص، مثل أصل رقمي، واسم، ووصف، وسمات أخرى.
سنحتاج إلى تكوين هذه البيانات الوصفية ككائن JSON وتخزينها، حتى نتمكن من تمريرها كمعلمة tokenURI عند استدعاء وظيفة mintNFT الخاصة بعقدنا الذكي.
سيشكل النص الموجود في حقول "Link to Asset"، و "Name"، و "Description" الخصائص المختلفة للبيانات الوصفية لـ NFT الخاص بنا. سنقوم بتنسيق هذه البيانات الوصفية ككائن JSON، ولكن هناك خياران لمكان تخزين كائن JSON هذا:
- يمكننا تخزينه على سلسلة الكتل لإيثيريوم؛ ومع ذلك، فإن القيام بذلك سيكون مكلفًا للغاية.
- يمكننا تخزينه على خادم مركزي، مثل AWS أو Firebase. لكن ذلك سيتعارض مع روح اللامركزية لدينا.
- يمكننا استخدام IPFS، وهو بروتوكول لامركزي وشبكة نظير إلى نظير لتخزين ومشاركة البيانات في نظام ملفات موزع. نظرًا لأن هذا البروتوكول لامركزي ومجاني، فهو خيارنا الأفضل!
لتخزين بياناتنا الوصفية على 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، ثم قم بتشغيل الأمر التالي في الطرفية الخاصة بك:
npm install dotenv --save
بعد ذلك، قم بإنشاء ملف .env في الدليل الجذر لـ minter-starter-files الخاص بك عن طريق إدخال ما يلي في سطر الأوامر الخاص بك:
vim.env
سيؤدي هذا إلى فتح ملف .env الخاص بك في vim (محرر نصوص). لحفظه، اضغط على "esc" + ":" + "q" على لوحة المفاتيح بهذا الترتيب.
بعد ذلك، في VSCode، انتقل إلى ملف .env الخاص بك وأضف مفتاح API الخاص بـ Pinata وسر API إليه، هكذا:
REACT_APP_PINATA_KEY = <pinata-api-key>
REACT_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 هكذا:
require("dotenv").config()
const key = process.env.REACT_APP_PINATA_KEY
const secret = process.env.REACT_APP_PINATA_SECRET
بعد ذلك، الصق الكود الإضافي من الأسفل في ملف pinata.js الخاص بك. لا تقلق، سنفصل ما يعنيه كل شيء!
require("dotenv").config()
const key = process.env.REACT_APP_PINATA_KEY
const secret = process.env.REACT_APP_PINATA_SECRET
const axios = require("axios")
export const pinJSONToIPFS = async (JSONBody) => {
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`
//إجراء طلب POST عبر axios إلى Pinata ⬇️
return axios
.post(url, JSONBody, {
headers: {
pinata_api_key: key,
pinata_secret_api_key: secret,
},
})
.then(function (response) {
return {
success: true,
pinataUrl:
"https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash,
}
})
.catch(function (error) {
console.log(error)
return {
success: false,
message: error.message,
}
})
}
إذن ماذا يفعل هذا الكود بالضبط؟
أولاً، يستورد axios (opens in a new tab)، وهو عميل HTTP قائم على الوعود (promise based) للمتصفح و Node.js، والذي سنستخدمه لتقديم طلب إلى Pinata.
ثم لدينا وظيفتنا غير المتزامنة pinJSONToIPFS، والتي تأخذ JSONBody كمدخل لها ومفتاح وسر API الخاص بـ Pinata في ترويستها (header)، كل ذلك لتقديم طلب POST إلى API pinJSONToIPFS الخاص بهم.
- إذا كان طلب POST هذا ناجحًا، فإن وظيفتنا تعيد كائن JSON مع القيمة المنطقية
successكـ true وpinataUrlحيث تم تثبيت بياناتنا الوصفية. سنستخدمpinataUrlالمُرجع هذا كمدخلtokenURIلوظيفة السك الخاصة بعقدنا الذكي. - إذا فشل طلب POST هذا، فإن وظيفتنا تعيد كائن JSON مع القيمة المنطقية
successكـ false وسلسلة نصيةmessageتنقل خطأنا.
كما هو الحال مع أنواع إرجاع وظيفة connectWallet الخاصة بنا، نحن نعيد كائنات JSON حتى نتمكن من استخدام معلماتها لتحديث متغيرات الحالة وواجهة المستخدم الخاصة بنا.
تحميل عقدك الذكي
الآن بعد أن أصبح لدينا طريقة لتحميل البيانات الوصفية لـ NFT الخاص بنا إلى IPFS عبر وظيفة pinJSONToIPFS الخاصة بنا، سنحتاج إلى طريقة لتحميل مثيل لعقدنا الذكي حتى نتمكن من استدعاء وظيفة mintNFT الخاصة به.
كما ذكرنا سابقًا، في هذا البرنامج التعليمي سنستخدم هذا العقد الذكي الحالي لـ NFT (opens in a new tab)؛ ومع ذلك، إذا كنت ترغب في معرفة كيف صنعناه، أو صنع واحد بنفسك، نوصي بشدة بالاطلاع على برنامجنا التعليمي الآخر، "كيفية إنشاء NFT." (opens in a new tab).
ABI الخاص بالعقد
إذا فحصت ملفاتنا عن كثب، ستلاحظ أنه في دليل src الخاص بنا، يوجد ملف contract-abi.json. يعد ABI ضروريًا لتحديد الوظيفة التي سيستدعيها العقد بالإضافة إلى ضمان أن الوظيفة ستعيد البيانات بالتنسيق الذي تتوقعه.
سنحتاج أيضًا إلى مفتاح API الخاص بـ Alchemy و API الخاص بـ Alchemy Web3 للاتصال بسلسلة الكتل لإيثيريوم وتحميل عقدنا الذكي.
إنشاء مفتاح API الخاص بـ Alchemy
إذا لم يكن لديك حساب Alchemy بالفعل، قم بالتسجيل مجانًا هنا. (opens in a new tab)
بمجرد إنشاء حساب Alchemy، يمكنك إنشاء مفتاح API عن طريق إنشاء تطبيق. سيسمح لنا هذا بتقديم طلبات إلى شبكة اختبار روبستن.
انتقل إلى صفحة "Create App" في لوحة تحكم Alchemy الخاصة بك عن طريق التمرير فوق "Apps" في شريط التنقل والنقر على "Create App".
قم بتسمية تطبيقك، لقد اخترنا "My First NFT!"، وقدم وصفًا قصيرًا، وحدد "Staging" للبيئة المستخدمة لمسك دفاتر تطبيقك، واختر "Ropsten" لشبكتك.
انقر على "Create app" وهذا كل شيء! يجب أن يظهر تطبيقك في الجدول أدناه.
رائع، الآن بعد أن أنشأنا عنوان URL الخاص بـ HTTP Alchemy API، انسخه إلى الحافظة الخاصة بك...
…ثم دعونا نضيفه إلى ملف .env الخاص بنا. إجمالاً، يجب أن يبدو ملف .env الخاص بك هكذا:
REACT_APP_PINATA_KEY = <pinata-key>
REACT_APP_PINATA_SECRET = <pinata-secret>
REACT_APP_ALCHEMY_KEY = https://eth-ropsten.alchemyapi.io/v2/<alchemy-key>
الآن بعد أن أصبح لدينا ABI الخاص بالعقد ومفتاح API الخاص بـ Alchemy، نحن جاهزون لتحميل عقدنا الذكي باستخدام Alchemy Web3 (opens in a new tab).
إعداد نقطة نهاية Alchemy Web3 والعقد الخاص بك
أولاً، إذا لم يكن لديك بالفعل، فستحتاج إلى تثبيت Alchemy Web3 (opens in a new tab) عن طريق الانتقال إلى الدليل الرئيسي: nft-minter-tutorial في الطرفية:
cd ..
npm install @alch/alchemy-web3
بعد ذلك دعونا نعود إلى ملف interact.js الخاص بنا. في الجزء العلوي من الملف، أضف الكود التالي لاستيراد مفتاح Alchemy الخاص بك من ملف .env الخاص بك وإعداد نقطة نهاية Alchemy Web3 الخاصة بك:
require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)
Alchemy Web3 (opens in a new tab) هو غلاف حول Web3.js (opens in a new tab)، يوفر طرق API محسنة وفوائد حاسمة أخرى لجعل حياتك كمطور Web3 أسهل. تم تصميمه ليتطلب الحد الأدنى من التكوين حتى تتمكن من البدء في استخدامه في تطبيقك على الفور!
بعد ذلك، دعونا نضيف ABI الخاص بالعقد وعنوان العقد إلى ملفنا.
require("dotenv").config()
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
const web3 = createAlchemyWeb3(alchemyKey)
const contractABI = require("../contract-abi.json")
const contractAddress = "0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE"
بمجرد أن يكون لدينا كلاهما، نحن جاهزون للبدء في كتابة كود وظيفة السك الخاصة بنا!
تنفيذ وظيفة mintNFT
داخل ملف interact.js الخاص بك، دعونا نحدد وظيفتنا، mintNFT، والتي ستقوم بسك NFT الخاص بنا كما يوحي اسمها.
لأننا سنجري العديد من الاستدعاءات غير المتزامنة (إلى Pinata لتثبيت بياناتنا الوصفية على IPFS، و Alchemy Web3 لتحميل عقدنا الذكي، وميتاماسك لتوقيع معاملاتنا)، ستكون وظيفتنا أيضًا غير متزامنة.
المدخلات الثلاثة لوظيفتنا ستكون url للأصل الرقمي الخاص بنا، و name، و description. أضف توقيع الوظيفة التالي أسفل وظيفة connectWallet:
export const mintNFT = async (url, name, description) => {}
معالجة أخطاء الإدخال
بطبيعة الحال، من المنطقي أن يكون هناك نوع من معالجة أخطاء الإدخال في بداية الوظيفة، لذلك نخرج من هذه الوظيفة إذا لم تكن معلمات الإدخال الخاصة بنا صحيحة. داخل وظيفتنا، دعونا نضيف الكود التالي:
export const mintNFT = async (url, name, description) => {
//معالجة الأخطاء
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
}
بشكل أساسي، إذا كان أي من معلمات الإدخال عبارة عن سلسلة نصية فارغة، فإننا نعيد كائن JSON حيث تكون القيمة المنطقية success كـ false، وتنقل السلسلة النصية status أنه يجب إكمال جميع الحقول في واجهة المستخدم الخاصة بنا.
تحميل البيانات الوصفية إلى IPFS
بمجرد أن نعرف أن بياناتنا الوصفية منسقة بشكل صحيح، فإن الخطوة التالية هي تغليفها في كائن JSON وتحميلها إلى IPFS عبر pinJSONToIPFS التي كتبناها!
للقيام بذلك، نحتاج أولاً إلى استيراد وظيفة pinJSONToIPFS إلى ملف interact.js الخاص بنا. في الجزء العلوي من interact.js، دعونا نضيف:
import { pinJSONToIPFS } from "./pinata.js"
تذكر أن pinJSONToIPFS تأخذ جسم JSON. لذا قبل أن نجري استدعاءً لها، سنحتاج إلى تنسيق معلمات url، و name، و description الخاصة بنا في كائن JSON.
دعونا نحدث الكود الخاص بنا لإنشاء كائن JSON يسمى metadata ثم نجري استدعاءً لـ pinJSONToIPFS باستخدام معلمة metadata هذه:
export const mintNFT = async (url, name, description) => {
//معالجة الأخطاء
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
//إنشاء بيانات وصفية
const metadata = new Object()
metadata.name = name
metadata.image = url
metadata.description = description
//إجراء استدعاء Pinata
const pinataResponse = await pinJSONToIPFS(metadata)
if (!pinataResponse.success) {
return {
success: false,
status: "😢 Something went wrong while uploading your tokenURI.",
}
}
const tokenURI = pinataResponse.pinataUrl
}
لاحظ، نقوم بتخزين استجابة استدعائنا لـ pinJSONToIPFS(metadata) في كائن pinataResponse. ثم، نقوم بتحليل هذا الكائن بحثًا عن أي أخطاء.
إذا كان هناك خطأ، فإننا نعيد كائن JSON حيث تكون القيمة المنطقية success كـ false وتنقل السلسلة النصية status الخاصة بنا أن استدعاءنا فشل. بخلاف ذلك، نستخرج pinataURL من pinataResponse ونخزنه كمتغير tokenURI الخاص بنا.
الآن حان الوقت لتحميل عقدنا الذكي باستخدام API الخاص بـ Alchemy Web3 الذي قمنا بتهيئته في الجزء العلوي من ملفنا. أضف سطر الكود التالي إلى أسفل وظيفة mintNFT لتعيين العقد في المتغير العام window.contract:
window.contract = await new web3.eth.Contract(contractABI, contractAddress)
آخر شيء يجب إضافته في وظيفة mintNFT الخاصة بنا هو معاملة إيثيريوم الخاصة بنا:
//إعداد معاملة إيثيريوم الخاصة بك
const transactionParameters = {
to: contractAddress, // مطلوب باستثناء أثناء نشر العقد.
from: window.ethereum.selectedAddress, // يجب أن يتطابق مع عنوان المستخدم النشط.
data: window.contract.methods
.mintNFT(window.ethereum.selectedAddress, tokenURI)
.encodeABI(), //إجراء استدعاء إلى عقد ذكي لـ NFT
}
//توقيع المعاملة عبر ميتاماسك
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
success: true,
status:
"✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
txHash,
}
} catch (error) {
return {
success: false,
status: "😥 Something went wrong: " + error.message,
}
}
إذا كنت معتادًا بالفعل على معاملات إيثيريوم، ستلاحظ أن الهيكل مشابه جدًا لما رأيته.
- أولاً، نقوم بإعداد معلمات معاملاتنا.
- يحدد
toعنوان المستلم (عقدنا الذكي) - يحدد
fromموقع المعاملة (عنوان المستخدم المتصل بميتاماسك:window.ethereum.selectedAddress) - يحتوي
dataعلى استدعاء لطريقةmintNFTالخاصة بعقدنا الذكي، والتي تتلقىtokenURIالخاص بنا وعنوان محفظة المستخدم،window.ethereum.selectedAddress، كمدخلات
- يحدد
- ثم، نجري استدعاء await،
window.ethereum.request,حيث نطلب من ميتاماسك توقيع المعاملة. لاحظ، في هذا الطلب، نحدد طريقة eth الخاصة بنا (eth_SentTransaction) ونمررtransactionParametersالخاص بنا. في هذه المرحلة، سيتم فتح ميتاماسك في المتصفح، ويطالب المستخدم بتوقيع المعاملة أو رفضها.- إذا كانت المعاملة ناجحة، ستعيد الوظيفة كائن JSON حيث يتم تعيين القيمة المنطقية
successإلى true وتطالب السلسلة النصيةstatusالمستخدم بالتحقق من Etherscan لمزيد من المعلومات حول معاملته. - إذا فشلت المعاملة، ستعيد الوظيفة كائن JSON حيث يتم تعيين القيمة المنطقية
successإلى false، وتنقل السلسلة النصيةstatusرسالة الخطأ.
- إذا كانت المعاملة ناجحة، ستعيد الوظيفة كائن JSON حيث يتم تعيين القيمة المنطقية
إجمالاً، يجب أن تبدو وظيفة mintNFT الخاصة بنا هكذا:
export const mintNFT = async (url, name, description) => {
//معالجة الأخطاء
if (url.trim() == "" || name.trim() == "" || description.trim() == "") {
return {
success: false,
status: "❗Please make sure all fields are completed before minting.",
}
}
//إنشاء بيانات وصفية
const metadata = new Object()
metadata.name = name
metadata.image = url
metadata.description = description
//طلب تثبيت Pinata
const pinataResponse = await pinJSONToIPFS(metadata)
if (!pinataResponse.success) {
return {
success: false,
status: "😢 Something went wrong while uploading your tokenURI.",
}
}
const tokenURI = pinataResponse.pinataUrl
//تحميل عقد ذكي
window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract();
//إعداد معاملة إيثيريوم الخاصة بك
const transactionParameters = {
to: contractAddress, // مطلوب باستثناء أثناء نشر العقد.
from: window.ethereum.selectedAddress, // يجب أن يتطابق مع عنوان المستخدم النشط.
data: window.contract.methods
.mintNFT(window.ethereum.selectedAddress, tokenURI)
.encodeABI(), //إجراء استدعاء إلى عقد ذكي لـ NFT
}
//توقيع المعاملة عبر ميتاماسك
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
})
return {
success: true,
status:
"✅ Check out your transaction on Etherscan: https://ropsten.etherscan.io/tx/" +
txHash,
}
} catch (error) {
return {
success: false,
status: "😥 Something went wrong: " + error.message,
}
}
}
هذه وظيفة عملاقة! الآن، نحتاج فقط إلى ربط وظيفة mintNFT الخاصة بنا بمكون Minter.js الخاص بنا...
ربط mintNFT بواجهتنا الأمامية Minter.js
افتح ملف Minter.js الخاص بك وقم بتحديث سطر import { connectWallet, getCurrentWalletConnected } from "./utils/interact.js"; في الأعلى ليكون:
import {
connectWallet,
getCurrentWalletConnected,
mintNFT,
} from "./utils/interact.js"
أخيرًا، قم بتنفيذ وظيفة onMintPressed لإجراء استدعاء await لوظيفة mintNFT المستوردة الخاصة بك وتحديث متغير الحالة status ليعكس ما إذا كانت معاملتنا قد نجحت أو فشلت:
const onMintPressed = async () => {
const { status } = await mintNFT(url, name, description)
setStatus(status)
}
نشر NFT الخاص بك على موقع ويب مباشر
هل أنت مستعد لجعل مشروعك مباشرًا ليتفاعل معه المستخدمون؟ تحقق من هذا البرنامج التعليمي (opens in a new tab) لنشر أداة السك (Minter) الخاصة بك على موقع ويب مباشر.
خطوة أخيرة...
اكتساح عالم سلسلة الكتل
أمزح فقط، لقد وصلت إلى نهاية البرنامج التعليمي!
للتلخيص، من خلال بناء أداة لسك NFT، لقد تعلمت بنجاح كيفية:
- الاتصال بمحفظة ميتاماسك عبر مشروع الواجهة الأمامية الخاص بك
- استدعاء وظائف العقد الذكي من واجهتك الأمامية
- توقيع المعاملات باستخدام ميتاماسك
من المفترض أنك ترغب في أن تكون قادرًا على التباهي بـ NFTs المسكوكة عبر تطبيقك اللامركزي (dapp) في محفظتك — لذا تأكد من الاطلاع على برنامجنا التعليمي السريع كيفية عرض NFT الخاص بك في محفظتك (opens in a new tab)!
وكما هو الحال دائمًا، إذا كان لديك أي أسئلة، فنحن هنا للمساعدة في ديسكورد Alchemy (opens in a new tab). لا يسعنا الانتظار لرؤية كيف ستطبق المفاهيم من هذا البرنامج التعليمي على مشاريعك المستقبلية!
آخر تحديث للصفحة: 3 أبريل 2026