تخطٍ إلى المحتوى الرئيسي

دليل تعليمي لأداة سك الرموز غير القابلة للاستبدال (NFT Minter)

Solidity
NFT
Alchemy
العقود الذكية
الواجهة الأمامية
Pinata
ERC-721
المستوى المتوسط
smudgil
6 أكتوبر 2021
27 دقيقة قراءة

أحد أكبر التحديات التي تواجه المطورين القادمين من خلفية 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-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 الأساسي الذي سنعمل عليه.

في الجزء العلوي من هذا الملف، لدينا متغيرات الحالة (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 - سلسلة نصية تخزن اسم NFT
  • description - سلسلة نصية تخزن وصف NFT
  • url - سلسلة نصية تمثل رابطًا للأصل الرقمي لـ NFT

بعد متغيرات الحالة، سترى ثلاث دوال غير منفذة: useEffect، و connectWalletPressed، و onMintPressed. ستلاحظ أن جميع هذه الدوال هي async، وذلك لأننا سنجري استدعاءات API غير متزامنة بداخلها! أسماؤها تدل على وظائفها:

1useEffect(async () => {
2 //TODO: implement // للقيام به: التنفيذ
3}, [])
4
5const connectWalletPressed = async () => {
6 //TODO: implement // للقيام به: التنفيذ
7}
8
9const 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>
14
15 <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 <input
23 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 <input
29 type="text"
30 placeholder="e.g., My first NFT!"
31 onChange={(event) => setName(event.target.value)}
32 />
33 <h2>✍️ Description: </h2>
34 <input
35 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 NFT
42 </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 obj
12 } 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 your
27 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";
3
4const Minter = (props) => {
5
6 //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 your
33 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)
5
6 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_KEY
3const secret = process.env.REACT_APP_PINATA_SECRET

بعد ذلك، الصق الكود الإضافي من الأسفل في ملف pinata.js الخاص بك. لا تقلق، سنفصل ما يعنيه كل شيء!

1require("dotenv").config()
2const key = process.env.REACT_APP_PINATA_KEY
3const secret = process.env.REACT_APP_PINATA_SECRET
4
5const axios = require("axios")
6
7export 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 axios
11 .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_KEY
3const { 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_KEY
3const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
4const web3 = createAlchemyWeb3(alchemyKey)
5
6const 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 }
9
10 //make metadata // إنشاء البيانات الوصفية
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //make pinata call // إجراء طلب Pinata
17 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.pinataUrl
25}
إظهار الكل

لاحظ، أننا نخزن استجابة استدعائنا لـ 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.methods
6 .mintNFT(window.ethereum.selectedAddress, tokenURI)
7 .encodeABI(), //make call to NFT smart contract // إجراء استدعاء للعقد الذكي لـ NFT
8}
9
10//sign the transaction via MetaMask // توقيع المعاملة عبر MetaMask
11try {
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 رسالة الخطأ.

إجمالاً، يجب أن تبدو دالة 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 }
9
10 //make metadata // إنشاء البيانات الوصفية
11 const metadata = new Object()
12 metadata.name = name
13 metadata.image = url
14 metadata.description = description
15
16 //pinata pin request // طلب تثبيت Pinata
17 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.pinataUrl
25
26 //load smart contract // تحميل العقد الذكي
27 window.contract = await new web3.eth.Contract(contractABI, contractAddress) //loadContract(); // loadContract();
28
29 //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.methods
34 .mintNFT(window.ethereum.selectedAddress, tokenURI)
35 .encodeABI(), //make call to NFT smart contract // إجراء استدعاء للعقد الذكي لـ NFT
36 }
37
38 //sign transaction via MetaMask // توقيع المعاملة عبر MetaMask
39 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

هل كانت تعليمات الاستخدام هذه مفيدة؟