آموزش ساخت برنامه غیرمتمرکز (Dapp) ترون
در این مقاله سعی داریم تا نحوهی ایجاد یک قرارداد هوشمند و برنامهی غیرمتمرکز ترون را با استفاده از زبان برنامهنویسی «سالیدیتی» به شما آموزش دهیم. علاوه بر آن، در انتها، برنامهی ساخته شده را روی شبکهی آزمایشی اجرا و تست میکنیم. این آموزش بر اساس مقاله اصلی ترون با عنوان «ساخت برنامههای غیرمتمرکز برای مبتدیها» تهیه شده است.
در ابتدا یک کیف پول افزونهی کروم ایجاد میکنیم، سپس بعد از نصب نرمافزارها و فایلهای ضروری، با دسترسی دادن کد برنامه به کیف پول، موجودی حساب، پهنای باند آن را نمایش خواهیم داد. برای تست این برنامهی غیرمتمرکز، فروشگاهی را راهاندازی خواهیم کرد که در آن امکان خرید با ارز دیجیتال ترون وجود خواهد داشت. با ما همراه باشید.
پیشنیازها
پیش از شروع باید برای درک بهتر این مطلب به پیشنیازهای زیر توجه کنید:
• اگر مرورگر کروم را در اختیار ندارید، میتوانید آن را از اینجا دانلود کنید.
• باید با زبانهای برنامهنویسی آشنایی مختصری داشته باشید و بتوانید از GitHub، ویرایشگر متن، محیط توسعه یکپارچه (IDE) و ترمینال سیستم عامل لینوکس استفاده کنید.
نگاه کلی
در بخش اول به بیان مقدمهای در خصوص برنامهی غیرمتمرکزی که قرار است با استفاده از برخی ابزارها و شبکه تست ترون (Shasta) ایجاد کنیم، خواهیم پرداخت. قصد داریم TronWeb را که مشابه Web3.js (برای اتصال به شبکهی اتریوم استفاده میشود) است را راهاندازی کنیم. TronWeb از طریق کیف پول ترونلینک ایجاد شده و اجازه میدهد تا از طریق پروتکل به تعامل با شبکه بپردازیم.
همچنین، یک حساب ترونلینک ایجاد و به تعدادی TRX آزمایشی دسترسی پیدا خواهیم کرد. سپس از طریق برنامهی غیرمتمرکزمان با ترونلینک و شبکهی شاستا، تعامل خواهیم داشت.
توجه: اگر افزونهی ترون لینک را نصب نکردهاید به مطلب آموزش ساخت کیف پول ترون لینک (TronLink) در مرورگر کروم مراجعه کنید.
شروع کار
• پس از آن که کیف پول ترونلینک را روی مرورگر خود نصب کردید و به آن وارد شدید، روی افزونهی آن کلیک کنید تا حسابتان را مشاهده کنید.
• بر روی گزینهی تنظیمات، واقع در بالا سمت راست کیف پول، کلیک کنید.
• گزینهی اول، تعویض نود (Switch node) را انتخاب کنید.
• شبکهی اصلی به طور پیشفرض mainnet انتخاب شده است. این شبکه، شبکهی اصلی ترون بوده که پس از اتمام این آموزش، در آن به اجرا و کاوش تراکنشهای مالی واقعی خواهیم پرداخت.
• اگر روی آن کلیک کنید، میتوانید شبکهی آزمایشی شاستا (Shasta) را در پایین سمت راست، زیر گزینهی Mainnet انتخاب کنید. این شبکهی آزمایشی را ترون برای آشنایی ما با زیرساخت بلاک چین و ایدههای آزمایشی بدون آن که متوجهی هیچ خطر مالی شویم، ارائه داده است.
• پس از انتخاب شبکهی شاستا، به صفحهی اصلی ترونلینک بروید.
• باید اسم حساب خودتان را در ترونلینک ببینید.
• با کلیک روی آن، تبی برای شما باز خواهد شد که میتوانید به حسابهای مختلفی وارد شوید، حساب جدید بسازید و یا آنها را بازیابی کنید. آدرس قرار گرفته در زیر اسم کیف پول خود را انتخاب و کپی کنید. این آدرس چیزی شبیه به این خواهد بود:
این آدرس عمومی به حساب شما این امکان را میدهد تا با بلاک چین تعامل داشته باشد. کاربران با داشتن این آدرس میتوانند به حساب شما ترون ارسال کنند.
اگرچه کیف پول شما به مرورگر اضافه شده اما همچنان در شبکهی بلاک چین قرار نگرفته است. بنابراین، برای انجام این کار باید حداقل یک تراکنش انجام دهیم. اجازه دهید تا با دریافت کوین آزمایشی ترون از شاستا، کیف پولمان را روی این شبکهی آزمایشی قرار دهیم. برای به دست آوردن ترون آزمایشی:
• به سایت trongrid.io رفته و در قسمت پایین، بخش آدرس کیف پول آزمایشی (Test wallet address)، آدرس کیف پول خود را paste کرده و دکمهی Submit را فشار دهید.
• باید عبارت «درخواست شما با موفقیت ثبت شد، لطفاً کیف پول خود را بررسی کنید» را در زیر جایی که آدرس حساب خودتان را paste کردید، مشاهده کنید.
• اگر حسابتان را در ترونلینک بررسی کنید، خواهید دید که به آن ۱۰ هزار ترون ارسال شده و اکنون شما ۵ هزار پهنای باند روزانه دارید. (در آموزش کیف پولهای ترون در این خصوص صحبت شده است)
در اینجا کار به اتمام میرسد و اکنون برای تعامل و انجام آزمایش روی شبکهی آزمایشی شاستا آماده هستیم. شما میتوانید با رفتن به این آدرس، کد مربوط به ترونلینک را مشاهده کنید.
نصب NPM
مطمئن شوید که NPM را نصب کردهاید. با رفتن به این لینک و در پیش گرفتن دستورالعملهای گفتهشده، میتوانید آن را نصب کنید. پیشنهاد میشود که از نسخهی NVM آن استفاده کنید. اگر در این خصوص سوالی داشتید در قسمت نظردهی مطلب با ما در میان بگذارید.
نصب TronBox
ترون باکس ابزاری است که توسط تیم TRON توسعه داده شده و به شما کمک میکند تا قراردادهایتان را به سرعت کامپایل و مستقر کنید. حال که NPM را نصب نمودید، میتوانید ترون باکس را با باز کردن صفحهی ترمینال و نوشتن دستور npm install -g tronbox روی دستگاه خود نصب کنید.
حال نوشتن کد برنامهی غیرمتمرکز ترون را آغاز میکنیم.
راهاندازی پروژه
۱) فایلهای موجود در این آدرس را دانلود و یا Clone کنید.
۲) در صفحهی ترمینال، با وارد کردن عبارت cd، به دایرکتوری جدید بروید.
۳) دستور npm install را وارد کنید.
۴) پروژه را در ویرایشگر متن و یا محیط برنامهنویسی دلخواهتان باز کنید.
نصب فایلی مهم
• در دایرکتوری روت پروژه، فایلی به نام env. ایجاد کنید.
• از این فایل برای ذخیرهی اطلاعاتی که نمیخواهیم فاش شوند، استفاده میکنیم.
• این فایل را باز و دستور زیر را در آن paste کنید.
• سپس کلید خصوصیتان را در فایل env. بچسبانید.
برای اطلاع از کلید خصوصی، روی افزونهی ترونلینک کلیک کنید.
۱- وارد تب Account شوید.
۲- روی گزینهی Export کلیک کنید تا پاپ آپی که این کلید خصوصی را در بر دارد را مشاهده کنید.
• این کلید را کپی و در فایل env. بچسبانید.
• در آخر نیز اگر میخواهید این پروژه را در گیتهاب منتشر کنید، env. را به فایل gitignore. خود اضافه کنید.
ترونلینک و ترونوب در عمل
• با وارد کردن دستور npm run start در ترمینال، این برنامه را اجرا کنید.
• حال برنامه باید در مرورگر وب شما اجرا شود و چیزی شبیه به این تصویر را خواهید دید.
• در مرورگر، سمت بالا راست، باید بخش اطلاعات حساب (Account Information) را ببینید. این اولین بخشی است که راهاندازی خواهید کرد.
• در ویرایشگر متن، به مسیر /src/components/TronLinkInfo/index.js. بروید.
واکشی آدرس کیف پول:
• فراخوان تابع componentDidMount را از حالت کامنت خارج کنید.
• اگر به مرورگر بازگردید، آدرس حسابتان را بر مبنای ۱۶ ملاحظه خواهید کرد. اگر میخواهید این آدرس به فرمت Base58 نمایش داده شود، خطوط ۲۹ تا ۳۲ را از حالت کامنت خارج کرده و تابع setState واقع در خط ۳۲ را این گونه تغییر دهید:
this.setState ({ accountAddress: accountAddressInBase۵۸ });
• اکنون میتوانید در مرورگرتان، آدرس را به فرمت اسکی مشاهده کنید.
• حال بر روی افزونهی ترونلینک کلیک کرده و تائید کنید که آدرس نشان داده شده مربوط به آدرس حساب شماست.
واکشی موجودی حساب
مشابه قبل، تابع fetchAccountBalance واقع در خط ۳۷ تا ۴۶ را از حالت کامنت خارج کنید.
حال فراخوان تابع (componentDidMount) واقع در خط ۱۷ را از حالت کامنت در بیاورید.
در مرورگر باید موجودی حسابتان را به فرم SUN ببینید. برای نمایش این موجودی به فرم ترون، تابع setState را این گونه تغییر دهید و از حالت کامنت نیز آن را خارج کنید:
this.setState ({ accountBalance: balanceInTRX });
واکشی پهنای باند
• تابع fetchAccountBandwidth واقع در خطوط ۴۹ تا ۵۵ را از حالت کامنت خارج کنید.
• حال فراخوان تابع واقع در خط ۱۷ را از حالت کامنت در بیاورید.
• اکنون باید در مرورگر، موجودی پهنای باند حسابتان را مشاهده کنید.
• برنامهی شما باید چنین شکلی داشته باشد:
تبریک میگوییم، قسمت اول این آموزش را با موفقیت به پایان رساندید. در بخش بعد، پیش از آن که فرانت-اند برنامه را به قراردادهای هوشمندمان در بلاک چین متصل کنیم، به نوشتن این قرارداد میپردازیم.
بخش دوم
در این بخش به بررسی ابزارهایی که با آنها قراردادهای هوشمندمان را میسازیم، خواهیم پرداخت. قراردادهای هوشمند، راهی کارآمد برای اجرای تراکنشهای آنلاین بدون نیاز به خدمات از سوی اشخاص و واسطهها محسوب میشوند. برای پیادهسازی قراردادها، از زبان سطح بالا و قرارداد محوری به نام سالیدیتی (Solidity) استفاده میکنیم.
همچنین از ریمیکس (Remix) که ابزار منبع باز و قدرتمندی برای نوشتن قراردادهای هوشمند سالیدیتی به طور مستقیم از طریق مرورگر است، استفاده میکنیم. ریمیکس برای اتریوم و به زبان جاوا اسکریپت نوشته شده و از آن در مرورگر و انجام تستهای محلی میتوان استفاده کرد. حال که با این قرارداد آشنا شدید، اجازه دهید تا نوشتن اولین قرارداد هوشمند را آغاز کنیم.
ریمیکس (Remix)
مقدمه
در ابتدا به لینک بروید. با چنین صفحهای مواجه خواهید شد.
در اینجا به بررسی برخی از بخشهای مهم ریمیکس خواهیم پرداخت. در بخش file explorer در بالا سمت چپ، فایلهایی که در ریمیکس ایجاد کردهاید را خواهید دید. در دایرکتوری مرورگر، قراردادی که ایجاد کردهاید را مشاهده خواهید کرد.
در بخش میانی که ویرایشگر متن و یا محیط برنامهی ریمیکس نام دارد، فایلهای قراردادهایتان کامپایل خواهند شد. در اینجا همچنین میتوانید دستورات نحوی کلیدی هایلایت شده را مشاهده کنید.
در زیر آن نیز ترمینال قرار دارد که برای دیدن لاگ تراکنشها، تعامل با RemixIDE و اشکالزدایی کاربرد دارد. در سمت راست، تبهای کامپایل، اجرا و تست، اشکالزدایی، تنظیمات و پشتیبانی وجود دارند که به معرفی سه مورد از آنها میپردازیم.
• در تب کامپایل، نسخهی کامپایلری که میخواهیم ریمیکس از آن استفاده کند و همچنین برخی از گزینههای تنظیمات را برای تسهیل روند کار فعال میکنیم.
• در تب اجرا نیز به استقرار و تعامل با قراردادهایمان میپردازیم. همچنین میتوانیم تعیین کنیم که ریمیکس به کدام محیط متصل شود. در حال حاضر با ماشین مجازی جاوا اسکریپت کار خواهیم کرد زیرا دو محیط دیگر به ابزارهای خارجی نیاز دارند. ارائهدهندهی Web3 به نود اتریوم و ارائهدهندهی Injected نیز به متامسک (MetaMask) و یا Mint احتیاج دارد.
• در تب اشکالزدایی نیز میتوانید در صورت بروز مشکل، به بررسی کد و اشکالزدایی قرارداد هوشمندتان بپردازید.
راهاندازی
اجازه دهید ابتدا یک فایل قرارداد جدید ایجاد کنیم. (به یاد داشته باشید که تمام فایلهای سالیدیتی پسوند sol. دارند)
• در سمت بالا راست، کنار بخش کاوش فایل، روی دایرهای که علامت + دارد کلیک کنید تا فایل جدیدی ایجاد شود. در این هنگام با پاپ آپی روبرو خواهید شد که از شما میخواهد نام پیشفرض Untitled.sol را تغییر دهید. میتوانید اسم فایل را هرچه که دوست دارید بگذارید، اما به یاد داشته باشید که طبق یک قاعده کلی، باید پس از آن که قرارداد در آن تعریف شد، آن را نامگذاری کرد.
• نام این فایل را به عنوان مثال ECommerce.sol میگذاریم زیرا نام قرارداد ما قرار است Ecommerce باشد.
• پس از ثبت موفقیتآمیز نام فایل، باید فایل خالی (browser/ECommerce.sol) را در ویرایشگر متن مشاهده کنید.
• در تب کامپایل، در منوی کشویی که گزینهی انتخاب نسخهی کامپایلر جدید را نشان میدهد.
• گزینهی 0.4.24+commit.e67f0147 را انتخاب کنید. در بالای آن، عبارت Current version:۰.۴.۲۴+commit.e۶۷f۰۱۴۷.Emscripten.clang و در زیر آن گزینهی Auto compile را خواهید دید. این گزینه قرارداد هوشمند ما را درصورت تغییر، دوباره کامپایل میکند.
تب اجرا
• محیط انتخابی باید JavaScript VM باشد.
• توجه داشته باشید که هش قرار گرفته در کنار عبارت (۱۰۰ Ether)، مشابه آدرس عمومی موجود در ترونلینک، آدرس عمومی این حساب است.
• درست در کنار آن، گزینهی کپی کردن آدرس حساب قرار دارد.
• حد گس (Gas limit) به طور پیشفرض روی ۳ میلیون خواهد بود.
• مقدار (Value) نیز ۰ و نام واحد آن باید Wei (وِی) باشد. نام واحدهای ترون، TRX یا SUN است.
• پس از راهاندازی قرارداد در ریمیکس، با ایجاد تغییرات لازم، آن را با پروتکل ترون سازگار خواهیم کرد.
نوشتن قرارداد
در سمت راست، کادر زرد رنگ اخطار را خواهید دید که در آن نوشته شده است:
browser/ECommerce.sol:۱:۱: Warning: Source file does not specify required compiler version! Consider adding “pragma solidity ^۰.۵.۱
اگر تا کنون دستورالعملهای گفتهشده را در پیش گرفته باشید، باید نسخهی فعلی کامپایلر را روی «0.4.24+commit.e67f0147» تنظیم کرده باشید. این خطا به این دلیل رخ داده که فایل ما خالی است و نسخهی کامپایلر را مشخص نمیکند. در این برنامه، این نسخه، در فایل package.json، با عنوان «”solc”: “^۰.۴.۲۴» مشخص شده است. میتوان این مشکل را با اضافه کردن عبارت زیر به خط اول قرارداد، برطرف کرد:
pragma solidity ^۰.۴.۲۳;
کلید واژهی pragma نسخهی سالیدیتی که در فایل منبع از آن استفاده شده را مشخص میکند. نسخهی مشخصشده در اینجا، باید از نسخهای که در تب کامپایل تعیین شده کمتر باشد، بنا بر این به جای ۲۴. ، ۲۳. مینویسیم.
نامگذاری قرارداد
دستور زیر را در پایین مشخصات نسخهی کامپایلر وارد کنید.
contract ECommerce { }
• عبارت contract مشخصهی نوع قرارداد هوشمند ECommerce است.
• تمامی متدهای قرارداد، میان این دو کروشه قرار خوهند گرفت.
در سمت راست یک کادر سبز رنگ میبینید که درون آن ECommerce نوشته شده است.
• این پیغام اگر سبز رنگ بود بدان معناست که درست پیش میروید. اگر زرد بود بدان معناست که هشداری وجود دارد و بهتر است نگاهی به آن بیندازید. اما اگر قرمز بود حتما اشکالی وجود دارد و برای کامپایل کردن، باید آن را حل کرد.
• تمامی این دستورات را باید درون contract ECommerce انجام داد.
تعریف متغیرها: سالیدیتی به نوع متغیر وابسته است. بنابراین نوع دادهها (string, uint و غیره) باید به طور واضع تعریف شوند. برای اطلاع از انواع داده به این لینک بروید.
Struct روشی است که در سالیدیتی نوع دادهی جدید را تعریف میکند. همانند یک فروشگاه آنلاین، باید اجناسی را بفروشیم. اجازه دهید تا یک Item درون ECommerce contract تعریف کنیم.
struct Item { }
نوع دادهی Item ما به جزییاتی مانند شناسه، نام، قیمت، فروشنده و خریدار نیاز دارد. این موارد را به آن اضافه میکنیم:
uint id;
نوع id را از نوع unit تعیین میکنیم.
string name; uint price; bool available; address seller; address buyer;
حال کد شما بدین صورت خواهد شد:
struct Item { uint id; string name; uint price; bool available; address seller; address buyer; }
نگاشت را میتوان با جدول هش بسیاری از زبانها مشابه دانست. مقداردهی اولیه در سالیدیتی بدین صورت است که بایتهای هر کلید، ۰ در نظر گرفته خواهد شد. این دستور را زیر Item struct اضافه کنید:
mapping (uint => Item) items;
• از نگاشت (mapping) برای ذخیرهی Item structها استفاده میکنیم که در آن، id به عنوان کلید و Item struct به عنوان مقدار در نظر گرفته خواهند شد.
• بدین صورت میتوانیم آیتمها را رهگیری کنیم و پیش از افزودن آیتم به فروشگاه، بررسی کنیم که آیا کالایی با این شناسه وجود دارد یا خیر.
این روند ممکن است ما را به چنین چالش روبرو کند:
۱- میدانیم هنگامی که نگاشت ما مقداردهی اولیه میشود، تمام کلیدها وجود دارند. این بدان معناست که اگر میخواهیم به آیتمی با استفاده از کلید آن دسترسی پیدا کنیم که از روی عمد آن را اضافه نکردهایم، در بسیاری از زبانهای دیگر، مقدار null/nil/undefined برنخواهد گشت، بلکه ۰ نشان داده خواهد شد.
۲- یکی از روشهای حل این مشکل، افزودن متغیر exists به Item struct است. بدین صورت عمل خواهیم کرد:
struct Item { uint id; string name; uint price; bool available; address seller; address buyer; bool exists; }
میتوان به راحتی با بررسی آن که آیا مقدار متغیر exists برای آیتمی TRUE هست یا خیر، به آن دسترسی پیدا کرد.
تمامی آیتمها
اجازه دهید تا تعدا کل آیتمهای موجود در نگاشت آیتم را بررسی کنیم. برای این کار در زیرِ تعریف نگاشت، این دستور را بنویسید:
uint totalItems;
totalItems در حال حاضر مقدارش صفر است زیرا در سالیدیتی مقدار پیش فرض متغیرها صفر است.
رویدادها
طبق مستندات سالیدیتی، رویدادها سبب میشوند تا از امکانات لاگ EVM راحتتر بتوان استفاده کرد و در رابط کاربری برنامهی غیرمتمرکز از آن برای فراخوانیهای جاوا اسکریپت که وظیفهی انتظار برای فعال شدن یک رویداد را بر عهده دارند، استفاده خواهد شد.
اساسا هنگامی که یک آیتم را اضافه و یا خریداری میکنیم، میخواهیم این رویدادها را فعال کنیم. رویدادها، دادههای مفیدی را برای فرانت-اند برنامهی غیرمتمرکز ما به همراه خواهند داشت. رویدادها را با نوشتن نوع و سپس نام آنها، تعریف میکنیم. در پرانتز نیز نوع و نام آرگومانهای آن را مشخص میکنیم.
این رویدادها را در زیرِ totalItems وارد کنید:
event Purchased(uint id, string name, address indexed seller, address indexed buyer, uint price); event Added(uint id, string name, uint price, address indexed seller, bool available, bool exists); event Total(uint totalItems); event Availability(bool available);
سازندهها
• سازندهها و متغیرهای آن به همراه مقادیر پیشفرض، برای مقداردهی اولیهی قرارداد مورد استفاده قرار میگیرند.
• استفاده از سازنده اختیاری است. اگر نمیخواهید قراردادتان را با مقادیر پیشفرض مقداردهی کنید، از آن استفاده نکنید.
• ما از این سازندهها استفاده میکنیم تا مطمئن شویم هنگامی که قرارداد را مقدار میدهیم، totalItems صفر است.
این دستورات را نیز اضافه کنید:
event Purchased(uint id, string name, address indexed seller, address indexed buyer, uint price); event Added(uint id, string name, uint price, address indexed seller, bool available, bool exists); event Total(uint totalItems); event Availability(bool available);
یک تابع را میتوان به صورت خصوصی (private)، عمومی (public)، داخلی (internal) و یا خارجی (external) تعریف کرد.
• عمومی: در این صورت تابع میتواند توسط هر تابع و یا کلاس دیگری مورد استفاده قرار گیرد. این ویژگی، پیشفرض همهی توابع است.
• خصوصی: تابع تنها میتواند درون قرارداد مورد استفاده قرار گیرد.
• داخلی: این تابع تنها میتواند درون قرارداد و یا قرارداهایی که آن را به ارث میبرند، استفاده شود.
• خارجی: از این تابع تنها میتوان در بیرون از برنامه استفاده کرد و با استفاده از دستورات بارگذاری کتابخانه، آنها را وارد برنامه کرد.
توابع قرارداد
حال که قرارداد را نوشتیم، توابعی را برای تعامل با آن ایجاد میکنیم.
function checkItemsTotal() public returns (uint total) { emit Total(totalItems); return totalItems; }
• checkItemsTotal: بررسی میکند که چه تعداد آیتم درون فروشگاه هست.
• public بدان معناست که در همهی جای برنامه میتوان تابع checkItemsTotal را صدا زد.
• return: یک متغیر از جنس unit را برمیگرداند که در اینجا منظور کل آیتمهاست.
گزینهی addItem به ما این امکان را میدهد تا در فروشگاه آیتم اضافه کنیم.
function addItem (string _name, uint _price) public returns (bool success, uint id, string name, uint price, address seller, bool available) { }
• function نشان میدهد که یک تابع را تعریف میکنیم.
• addItem نام تابع ماست.
• (string _name, uint _price) به ترتیب نوع ورودی و مقادیر هستند. علامت زیرخط (_) نیز برای آن است که سالیدیتی متغیرهای جهانی و پارامترهای تابع را از هم تمییز دهد.
• عبارت public نشان میدهد که این تابع را میتوان در همه جای برنامه فرا خواند.
این موارد را به تابع addItem اضافه کنید:
uint itemId = totalItems;
• این دستور، بر اساس totalItems در فروشگاه، یک شناسهی یکتا برای هر آیتم میسازد.
• با استفاد از دستورات زیر میتوان اطمینان پیدا کرد که آیتم در نگاشت items نیست؛ بدین صورت که پیش از دادن شناسهای یکسان به آیتمی که میخواهیم اضافه کنیم، وجود آن آیتم را بررسی میکنیم. همچنین بررسی میکنیم که نام آیتم خالی نباشد (با تبدیل رشته به بایت و بررسی طول آن) و قیمت آن نیز بیشتر از ۰ باشد.
require(!items[itemId].exists, "An item already exists at this ID."); require(bytes(_name).length > ۰, "Item name cannot be empty."); require(_price > ۰, "Price must be greater than zero (۰).");
• دستور زیر، متغیر sellerAddress را از نوع address قرار میدهد. در هر بار تعامل کاربر با قرارداد، این دستور اجرا میشود. این آدرس، آدرس عمومی حساب کاربر است.
• آیتم را به نگاشت اضافه کنید و آن را به کلید itemId اختصاص دهید. این دستور همچنین به متغیرهای Item مقدار اختصاص میدهد.
id: itemId, // unique id for the item name: _name, // name of the item, passed in from the user as a parameter of the function available: true, // set to true (available) as it was just added to the store price: (_price * ۱۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰), // price of the item converted to WEI, passed in from the user as a parameter seller: sellerAddress, // address of the person that added this item to the store buyer: ۰, // set to ۰ by default as nobody has bought the item exists: true // to allow checking if the item at an id exists in the mapping, set to true });
• دستور روبرو، با هر بار افزودن آیتم، به متغیر totalItems یکی اضافه میکند.
totalItems += ۱;
• این دستور، رویداد Added را زمانی که آیتمی را با موفقیت اضافه میکنیم، فعال میکند.
emit Added(items[itemId].id, items[itemId].name, items[itemId].price, items[itemId].seller, items[itemId].available, items[itemId].exists);
• دستور زیر مقادیری را که میتوانیم در فرانت-اند برنامه از آن استفاده کنیم برمیگرداند.
return (true, items[itemId].id, items[itemId].name, items[itemId].price, items[itemId].seller, items[itemId].available);
تابع checkItem
این گزینه اجازه میدهد تا پیش از خرید آیتم، وجود آن را در قرارداد بررسی کنیم.
function checkItem(uint _id) public returns (uint itemId, string name, uint price, bool available, address seller, address buyer, bool exists) { emit Availability(items[_id].available); return (items[_id].id, items[_id].name, items[_id].price, items[_id].available, items[_id].seller, items[_id].buyer, items[_id].exists); }
• رویداد Availability فعال خواهد شد.
• مقادیر مشخص شده بازگردانده خواهند شد.
تابع buyItem
• تابع buyItem شناسهی unit آیتم خریداری شده را میگیرد. این تابع، عمومی بوده (در همهی جای برنامه میتوان از آن استفاده کرد) و payable نیز است (اجازه میدهد که تابع، توکن دریافت کند)
• در صورت اجرای موفقیت آمیز، این تابع، bool success، آدرسهای فروشنده و خریدار را باز خواهد گرداند.
• عبارت Require برای آن است که از معتبر بودن تراکنش اطمینان پیدا کنیم و این موارد را بررسی خواهیم کرد:
۱- آیا شناسهی داده شده وجود دارد؟
۲- آیا آیتم مورد نظر موجود است؟
۳- آیا این آیتم فروشندهی معتبری دارد؟
۴- آیا در حال حاضر این آیتم فروشندهای ندارد؟
۵- آیا موجودی حساب برای خرید آیتمیهایی که با این تابع ارسال شده کافی است؟
اگر هر کدام از این موارد برقرار نبود، تراکنش برگشت (Revert) خواهد خورد. این برگشت، تراکنش را لغو خواهد کرد و هر گس یا TRON، انرژی و یا پهنای باند استفاده نشده را برخواهد گرداند. در ادامه:
• متغیر محلی buyerAddress_ را به msg.sender (آدرس شخصی که این تابع را فراخوانی کرده است) تخصیص میدهیم.
• تابع قرارداد handlePurchase_ برای اجرای خرید صدا زده میشود (در ادامه به این تابع خواهیم پرداخت)
• رویداد Purchased فعال شده و پارامترهای مشخصشده بر گردانده میشوند.
function buyItem(uint _id) public payable returns (bool success, uint id, string name, address seller, address buyer, uint price) { require(items[_id].exists == true, "This item does not exist. Please check the id and try again."); require(items[_id].available == true, "This item is no longer available."); require(items[_id].seller != ۰, "This item has no seller"); require(items[_id].buyer == ۰, "This item is no longer available"); require(items[_id].price == msg.value, "Not enough TRX to buy this item."); address _buyerAddress = msg.sender; _handlePurchase(_id, _buyerAddress, msg.value); emit Purchased(_id, items[_id].name, items[_id].seller, items[_id].buyer, items[_id].price); return (true, _id, items[_id].name, items[_id].seller, items[_id].buyer, items[_id].price); }
تابع handlePurchase
این تابع، خرید واقعی آیتم را اجرا میکند.
function _handlePurchase(uint _id, address _buyerAddress, uint _value) internal { items[_id].available = false; items[_id].buyer = _buyerAddress; items[_id].seller.transfer(_value); }
• تابع _handlePurchase یک تابع داخلی است (تنها این قرارداد میتواند از آن استفاده کند). توابع داخلی را معمولا با علامت _ نامگذاری میکنند.
• به آیتم availability در نگاشت items مقدار FALSE میدهیم.
• آدرس خریدار آیتم را درون buyerAddress_ میریزیم.
• توکنها (value_) را به متغیر فروشنده در آیتمها تخصیص میدهیم.
مراحل توابع در اینجا به پایان میرسد. حال پیش از آن که به برنامهی خودمان بپردازیم، توابع قرارداد را در ریمیکس بررسی میکنیم.
تست قرارداد در ریمیکس
۱- به تب Run در سمت راست بروید.
۲- محیط باید روی JavaScriptVM تنظیم شده باشد.
۳- پنج حساب آزمایشی پیشفرض به همراه ۱۰۰ اتر آزمایشی توسط Remix به شما داده شده است.
۴- حد گس روی ۳ میلیون wei و مقدار نیز روی صفر تنظیم شده است.
در این مرحله باید ECommerce را از بلاک بعدی، در بالای دکمهی Deploy، انتخاب کرد. برای این کار:
• روی دکمهی Deploy کلیک کنید.
• باید در بلاک قرارداها، چیزی شبیه به این را ببینید:
• روی Debug کلیک کنید تا توابع قراردادهایی که میخواهیم آزمایش کنیم، نمایان شوند. پیشنهاد میکنیم که به بررسی این توابع بپردازید تا از عملکردشان درک بهتری به دست بیاورید. به عنوان مثال:
۱- بررسی کنید که چه تعداد آیتم در فروشگاه موجود است (checkItemsTotal)
۲- به فروشگاه یک آیتم اضافه کنید (addItem)
۳- اطلاعات آیتمهایی که اضافه کردید را با استفاده از شناسهی آنها بازیابی کنید (checkItem). در این صورت اگر آیتمی وجود نداشته باشد ۰ خواهد داد.
۴- از checkItem استفاده کنید و بررسی کنید که آیا مقادیر آیتمهای خریداری شده تغییر کردهاند یا خیر.
تبریک مجدد! بخش دوم ساخت DApp نیز با موفقیت به پایان رسید و توانستید قرارداد هوشمندتان را بنویسید. در بخش بعدی قرارداد را روی بلاک چین مستقر و آن را به برنامه متصل خواهیم کرد.
بخش سوم
دیدگاه کلی
در این بخش قرار است قرارداد هوشمندمان را از ریمیکس وارد برنامهی خود کرده و به اصلاح آن بپردازیم تا با شبکهی ترون هماهنگ شود. پس از آن نیز، با استفاده از TronBox، قراردادمان را روی شبکهی آزمایشی شاستا قرار داده و آن را کامپایل میکنیم. در نهایت، قرارداد هوشمند را به فرانت-اند برنامهی غیرمتمرکز خود متصل کرده و خواهیم دید که چگونه میتوان با دیگر وبسایتها به تعامل پرداخت.
مرحلهی مقدماتی
۱- ابتدا ترمینال را باز کرده و به دایرکتوری روت بروید.
۲- برنامهی غیرمتمرکزتان را با ویراشگر متن و یا محیط برنامهنویسی (Atom, VSCode, etc) دلخواه باز کنید.
بارگذاری قرارداد در dApp
• در IDE، زیر فولدر روت، به دنبال دایرکتوری به نام contracts بگردید. در آن جا باید فایل Migrations.sol را مشاهده کنید.
• در این دایرکتوری، فایلی به نام ECommerce.sol بسازید و آن را باز کنید.
• کل قراردادتان را از ریمیکس کپی کرده و در فایل ECommerce.sol بچسبانید.
• در تابع addItem، عبارت
price: (_price * ۱۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰),
را به
price: (_price * ۱۰۰۰۰۰۰),
تغییر دهید زیرا ۱ اتر برابر 1e18 wei و ۱ TRX برابر ۱e۶ Sun است.
کامپایل و Migrate کردن
حال که قرارداد هوشمند را در برنامه داریم، میتوانیم به کامپایل و migrate کردن آن بپردازیم. در فایل migrations/2_deploy_contracts.js این تغییرات را ایجاد کنید.
• دستور زیر را به دستور دوم تغییر دهید.
var MyContract = artifacts.require("./MyContract.sol");
var ECommerce = artifacts.require("./ECommerce.sol");
• برای این دستور نیز به همان صورت عمل کنید:
deployer.deploy(MyContract);
deployer.deploy(ECommerce);
• در ترمینال، در روت dApp، دستور زیر را اجرا کنید:
tronbox compile - -compile-all
- فلگ compile-all- – ، تمامی قراردادها را برای کامپایل و یا دوباره کامپایل شدن فرا میخواند.
- در نهایت با چنین خروجی روبرو خواهید شد:
• اکنون این دستور را اجرا کنید:
tronbox migrate --reset --network shasta
این دستور به tronbox میگوید که از شبکهی شاستا استفاده کرده، شبکه را ریست و قراردادها را روی شبکه migrate کند.
• اگر بار اول است که migrate را انجام میدهید، نیازی نیست که از فلگ reset- – استفاده کنید.
• خروجی چنین شکلی خواهد داشت:
• دقت کنید که باید آدرس قرارداد ECommerce که در این مرحله به شما داده میشود را بردارید. این آدرس را هم در مبنای ۱۶ و هم به فرمت base58 خواهید دید. تنها به یکی از این آدرسها نیاز است، اما محض اطمینان هر دوی آنها را بردارید.
• به فایل src/components/ECommerce/index.js در ویرایشر متن خود بروید.
• تقریبا در بالای فایل، کادری را خواهید دید که میتوانید این آدرسها را در آن جا بچسبانید.
لینک کردن قرارداد هوشمند به فرانت-اند برنامهی غیرمتمرکز
• تمام کدهای کامنت شده را از حالت کامنت خارج کنید.
• خط زیر را با این دستورات زیر آن عوض کنید:
(<p>This will be the ECommerce Component </p>)
<div className="eCommerce-component-dash"> <div>Total Items In Store: {totalItems}</div> <button onClick="{this.checkItemsTotal}">Total Contract Items</button> <button onClick="{this.addItem}">Add Item</button> </div> <div className="eCommerce-item-container">{allItems}</div>
این دستور در انتهای فایل قرار دارد.
بررسی فرانت-اند برنامه
اضافه کردن فایلهای ضروری
• React را باید import کرد زیرا این برنامه از react استفاده میکند. Sweetalert نیز به ما اجازه میدهد تا هشدارهای دلخواهمان را نمایش دهیم.
• فایل utils نیز شامل توابعی است که برای تنظیم tronweb و آدرس قرارداد استفاده میشوند.
• eCommerceData جایی است که آیتمهای خود را به فرمت json در آن جا ذخیره کردهایم.
import React, { Component } from "react"; import Swal from "sweetalert۲"; import Utils from "../../utils"; import eCommerceData from "./eCommerce-data"; // items in json format import "./ECommerce.scss"; //styling
ذخیرهسازی
در کد زیر آدرس قراردادتان را ذخیره کنید و بعدها از آنها در برنامههایمان استفاده خواهیم کرد.
/// Add your contract address here//////////////////////////////// const contractAddress = "Your contract address here";
سازندهها
• constructor یک سازنده برای کلاس eCommerce است.
• در اینجا کامپوننتِ state و توابعِ bind کلاس را برای نگهداری محتوا، ذخیره میکنیم.
constructor(props) { super(props); this.state = { dataLength: eCommerceData.length, allItems: [], totalItems: ۰ }; this.addItem = this.addItem.bind(this); this.buyItem = this.buyItem.bind(this); this.checkItem = this.checkItem.bind(this); this.checkItemsTotal = this.checkItemsTotal.bind(this); }
همگامسازی
• هنگامی که کامپوننت اجرا شد، tronweb و قرارداد را همگام کنید.
async componentDidMount() { await Utils.setContract(window.tronWeb, contractAddress); }
تابع اضافه کردن آیتم
• ابتدا متغیرهای state کامپوننت را تعمیم خواهیم داد، سپس مطمئن میشویم که فایل json، آیتمهای بیشتری برای افزودن داشته باشد.
• یک متغیر قیمت طراحی میکنیم و به شناسه و قیمت آن مقدار تصادفی میدهیم.
• برای نمایش آیتم در مرورگر، یک html ایجاد خواهیم کرد و آن را به آرایهی آیتمهای کامپوننت state میفرستیم.
• تابع addItem قرارداد را برای افزودن آیتم به فروشگاه، فراخوانی میکنیم. مقادیر بازگردانده شده از سوی دستورات return قراردادها، بعدها با استفاده از sweetalert، برای نمایش هشدار در مررورگر، استفاده خواهند شد.
• در نهایت کل آیتمها را به کامپوننت state اضافه میکنیم.
addItem() { const { totalItems, dataLength, allItems } = this.state; if (totalItems >= dataLength) { Swal({ title: "No more items in data to add.", type: "error" }); return; } let item = eCommerceData[totalItems]; item.price = parseFloat(Math.random() * 10).toFixed(0); item.id = totalItems; allItems.push( <div className="eCommerce-item" key={item.id}> <img className="item-image" src={item.image} alt={item.name} /> <div className="item-name">{item.name}</div> <div className="price-buy-container"> <div className="item-price">{item.price} TRX</div> <button className="buy-button" onClick={() => this.buyItem(item.id, item.price)} > Buy </button> <button className="buy-button" onClick={() => this.checkItem(item.id)} > Check </button> </div> </div> ); Utils.contract .addItem(item.name, item.price) .send({ shouldPollResponse: true }) .then(res => { Swal.fire({ title: `${res.name} was added at index ${res.id}`, html: `<p>Price: ${res.price / ۱۰۰۰۰۰۰} TRX (${res.price} SUN)</p>` + `<p>Seller: ${res.seller}</p>` + `<p>Available: ${res.available}</p>`, type: "success" }); }) .catch(err => { console.log(err); Swal.fire({ title: "Unable to add item.", type: "error" }); }); this.setState({ totalItems: totalItems + ۱ }); }
تابع چک کردن موجودی آیتمها
تابع checkItemsTotal قراردادهایمان را فراخوانی خواهیم کرد و از مقادیر بازگردانده شده برای نمایش هشدار در مرورگر استفاده میکنیم. این دستور تعداد آیتمهای موجود در فروشگاه را به ما نشان خواهد داد.
checkItemsTotal() { Utils.contract .checkItemsTotal() .send({ callValue: ۰ }) .then(res => { Swal.fire({ title: `There are ${res.total} in this contract's store.`, type: "success" }); }) .catch(err => { console.log(err); Swal.fire({ title: "Something went wrong in checking the total.", type: "error" }); }); }
تابع چک کردن موجودی آیتم
آیتم را با شناسهی داده شده در قرارداد بررسی میکنیم و بار دیگر، از مقادیر بازگردانده شده، برای نمایش هشدار به کاربران استفاده میکنیم.
checkItem(id) { Utils.contract .checkItem(id) .send({ shouldPollResponse: true, callValue: ۰ }) .then(res => { Swal.fire({ title: `Available: ${res.available}.`, type: res.available ? "success" : "error" }); }) .catch(err => { console.log(err); Swal.fire({ title: "Unable to check item.", type: "error" }); }); }
تابع خرید آیتم
آیتمی را برای قراردادمان خریداری میکنیم. توجه داشته باشید که قیمت آیتم در یک میلیون ضرب شده تا به SUN تبدیل شود.
buyItem(id, price) { Utils.contract .buyItem(id) .send({ shouldPollResponse: true, callValue: price * ۱۰۰۰۰۰۰ //convert to SUN }) .then(res => { Swal.fire({ title: `You have purchased ${res.name} for ${res.price / ۱۰۰۰۰۰۰} TRX (${res.price} SUN).`, html: `<p>Seller: ${res.seller}</p>` + `<p>Buyer: ${res.buyer}</p>`, type: "success" }); }) .catch(err => { console.log(err); Swal.fire({ title: "Unable to purchase item.", type: "error" }); }); }
نمایش برنامهی نهایی
• در ترمینال، npm run start را اجرا کنید و پس از آن باید برنامه را در مرورگر ببینید.
• راهاندازی برنامه روی اینترنت، هیچ تفاوتی با راهاندازی یک برنامهی React ندارد.
اکنون توانستید یک برنامهی غیرمتمرکز بسازید و یک قرارداد هوشمند را روی شبکهی شاستا راهاندازی کنید. شما دیگر این توانایی را خواهید داشت تا به توسعهدهندهی dApp موفقی تبدیل شوید.
بخش چهارم
نگاهی اجمالی
در این بخش از آموزش، به بررسی رویدادهای برنامه میپردازیم. همچنین، کارایی رویدادها را در فرانت-اند خواهیم دید.
رویدادها
یکی از مهمترین اجزای برنامه، رویدادها هستند و هنگامی که تغییری در قرارداد رخ میدهد، واکنش نشان میدهند. مشابه متدهای () call و ()send که در متدهای قرارداد فراخوانی میشوند، ()watch نیز میتواند در رویدادهای قرارداد (Purchased, Added, Total, Availability) فراخوانی شود. برای شروع به این لینک رفته، کد را بردارید و آن را با کد موجود در فایل src/components/ECommerce/index.js جا به جا کنید.
بررسی فرانت-اند
تا کنون تمامی توابع توضیح داده شدند، بنابر این، تنها کد یکی از رویدادهای جدید را با هم بررسی میکنیم.
• تایع startEventListeners:
۱- این تابع جدیدی است که به کدمان اضافه کردیم. startEventListeners مسئول بررسی فعال شدن رویدادها توسط قراردادمان است.
۲- بهتر است هنگامی که یک آیتم اضافه و یا خریداری میشود و دیگر موجود نیست، از آن استفاده شود.
۳- دو تابع event listener را برای بررسی فعال شدن رویدادهای Purchased و Added که قراردامان فراخوانی میکند، آغاز میکنیم.
startEventListeners() { Utils.contract.Purchased().watch((err, { result }) => { if (err) return console.log("Failed to bind event listener", err); if (result) { Swal.fire({ title: `${result.name} has been purchased for ${result.price}.`, html: `<p>Seller: ${result.seller}</p>` + `<p>Buyer: ${result.buyer}</p>`, type: "success" }); } }); Utils.contract.Added().watch((err, { result }) => { if (err) return console.log("Failed to bind event listener", err); if (result) { Swal.fire({ title: `${result.name} has been added for ${result.price}.`, html: `<p>Seller: ${result.seller}</p>` + `<p>Added: ${result.exists}</p>` + `<p>Available: ${result.available}</p>`, type: "success" }); } }); }
جمعبندی
در این آموزش آموختید که چگونه با استفاده از زبان برنامه نویسی سالیدیتی، یک قرارداد هوشمند بنویسید. علاوه بر آن به بررسی تک تک توابع استفاده شده در آن پرداختیم. یک برنامهی غیرمتمرکز فروشگاه ساختیم و از طریق مرورگر به تعامل با آن پرداختیم. به راحتی میتوانید این مراحل را برای ارز دیجیتال اتریوم نیز انجام دهید. برای درک بهتر ساختار برنامه بهتر است تا حدی با زبانهای برنامه نویسی تحت وب آشنایی داشته باشید بنابراین توصیه میشود که پیش از شروع کار، به مطالعه در این خصوص بپردازید. گزینههای زیادی میتوان به این برنامه افزود که توضیح آنها از گنجایش این مطلب فراتر خواهد رفت. در آخر از شما عزیزان و همراهان متشکریم که تا پایان این آموزش ما را همراهی کردید.
سلام کلاهبرداران از طریق DApp دسترسی کامل به کیف پول را دارند و با سودهایی که در اول کار پرداخت میکنند ، پس از مدتی و جلب اعتماد طعمه های خود کل موجودی کیف پول را خالی میکنند . آنها طمع مردم را تحریک میکنند و با پرداخت پاداش های چشمگیر برای رفرال کردن اعضای جدید ، طعمه های بیشتری را از طریق هر شخصی پیدا میکنند ولی در نهایت چندین برابر سودی را که پرداخت کردن از قربانیانشان دزدی میکنند . خیلی مراقب باشید !!!!!