Truffle که توسط ConsenSys پشتیبانی می شود به سرعت در حال تبدیل شدن به چارچوب برتر برای توسعه بلاک چین می باشد. Truffle مانند یک چاقوی ارتشی سوئیسی بهترین نمونه ها و ابزار ها را کنار هم قرار می دهد تا ایجاد، ترکیب، تست و توسعه قرارداد هوشمند را بر روی بلاک چین اتریوم ساده و موثر کند. علاوه بر این از توسعه فرانت اند با استفاده از یک انبارش Redux پشتیبانی می کند که به طور خودکار با داده های قرارداد همگام می شود. بنابراین ادغام با React ساده و آسان است. برای استادان کهنه کاری که در روز های اول برنامه های غیر متمرکز را توسعه دادند، مزایای Truffle واضح و مشخص است. آنهایی هم که تازه وارد فضای توسعه برنامه های غیر متمرکز شده اند بدانند که در زمانی عالی وارد این فضا شده اند.
Truffle که به زبان جاوا اسکریپت نوشته شده است، ویژگی های کلیدی را مجزا می کند تا پیچیدگی و بار شناختی موجود را تسهیل کند. اما اگر ما برای هر چیزی متکی بر این چارچوب باشیم، درک نمی کنیم که چه مکانیسمی باعث می شود که بلاک چین اینقدر خاص باشد. هر فرد آماتوری با دنبال کردن رهنمود ها می تواند به کدگذاری در این زمینه بپردازد. از طرف دیگر تخصص و نوآوری نیازمند آگاهی از فرض های زیربنایی و خلاقیت لازم برای به چالش کشاندن آنهاست.
خوشبختانه به دلیل اینکه آن تماما جاوا اسکریپت است، ما می توانیم عملکرد های اصلی Truffle را از ابتدا تقلید کنیم. با شروع کردن از یک پروژه نود خالی، ما تلاش می کنیم که یک قرارداد هوشمند ایجاد کنیم، آن را سازماندهی و تست کنیم و در شبکه واقعی اتریوم نصب نماییم. ما آن را همچنین به یک سطح کاربری واقعی مرتبط خواهیم کرد. با انجام دادن چنین کاری، نویسنده امیدوار است که درک عمیق تری از این فناوری های اساسی را انتقال دهد که در نتیجه آن توسعه با استفاده از Truffle نوعی وابستگی نباشد بلکه نشان از یک انتخاب خلاقانه باشد.
یکی از مبادی اولیه عالی در این زمینه داشتن یک مدل ذهنی واضح از بسته برنامه اتریوم می باشد:
https://medium.com/heartbankacademy/a-complete-mental-model-for-ethereum-dapp-development-5ce08598ed0a?source=post_page—–f1ff6add416c———————-
مخزن پروژه ما: https://github.com/HeartBankAcademy/dApp
Truffle Init
از آنجا که توکن ها بخش اساسی اکوسیستم اتریوم هستند، سعی می کنیم یک توکن را از ابتدا بسازیم تا درک بهتری از مکانیسم عملکرد Truffle داشته باشیم. با استفاده از npm می توانیم شروع به راه اندازی یک پروژه نود خالی کنیم.
سپس فایل ها و فولدر های خالی را با این معماری ایجاد می کنیم:
مانند یک پروژه Truffle، فولدر contracts همه فایل های Solidity را منزل می دهد. معمولا این فایل ها باید با حروف بزرگ نوشته شود تا نشان داده شود که قرارداد های Solidity وابسته به طبقاتی هستند که نمونه های قرارداد از آنها ارائه شده است. در مورد ما، ان تنها Token.sol می باشد. فولدر migrations اسکریپت هایی را جای می دهد که برای سازماندهی و گسترش قرارداد هایمان مورد استفاده قرار می دهیم. اجرای compile.is باید همه فایل های Solidity را به صورت فایل های json متناظر در فولدر build ترکیب کند. در مورد ما، آن تنها Token.json می باشد.
وقتی deploy.js را اجرا می کنیم، بایت کد داخل فایل های json ما باید در شبکه مورد انتخاب ما گسترش یابد.
سرانجام، فولدر test همه فایل های تست ما را جای می دهد. در مثال ما، آن تنها Token.test.js می باشد.
به طور خلاصه، دایرکتوری پروژه ما باید مانند زیر به نظر برسد:
این مرحله معادل با اجرا می باشد:
که در این مرحله فایل ها و فولدر های زیر ایجاد می شوند:
به شباهت ها توجه داشته باشید.
توسعه Truffle
مانند Remix، Truffle دارای یک بلاک چین محلی می باشد که می توانیم در خلال توسعه مورد استفاده قرار دهیم.
وقتی که ما این فرمان را اجرا می کنیم، truffle یک شبکه محلی را بر پورت ۹۵۴۵ راه اندازی می کند و یک عبارت یادآوری را ایجاد می کند که ۱۰ اکانت خارجی اولیه و کلید های خصوصی از آن برای راحتی ما نمایش داده شده است.
Truffle در ناحیه عملکردی خود از یک نسخه کلی از ganache-cli استفاده می کند. اجازه دهید ابتدا آن را در مقیاس جهانی نصب کنیم و سپس یک شبکه محلی را برای دیدن مشابهت ها راه اندازی کنیم.
مانند Truffle، ganache-cli همچنین یک عبارت یادآور حافظه را ایجاد می کند و اولین ۱۰ اکانت خروجی و کلید های خصوصی را به نمایش می گذارد. بلاک ها به صورت پیش فرض فورا ماین می شوند.
استفاده از شبکه محلی فراهم شده توسط Truffle بر روی پورت ۹۵۴۵ اختیاری است. اگر ترجیح دهیم که کنترل بیشتری بر شبکه محلیمان داشته باشیم، می توانیم در عوض با انتقال به پورت ۸۵۴۵ از ganache-cli استفاده کنیم.
به عنوان مثال با ganache-cli می توانیم با مشخص کردن blocktime ۱۵ ثانیه یک شبکه واقعی تر را ایجاد کنیم.
یا اگر ما یک سطح کاربری گرافیکی را ترجیح می دهیم، می توانیم با انتقال به پورت ۷۵۴۵ از برنامه دسکتاپ ganache استفاده کنیم.
حال که عملکرد ganache-cli را درک می کنیم، اجازه دهید آن را به عنوان یک وابستگی پروژه ای نصب کنیم که در نتیجه می توانیم ان را برای استفاده بعدی وارد کنیم.
حال می توانیم کدگذاری قرارداد هوشمند خود را شروع کنیم.
کامپایل Truffle
توکن ها نماینده دارایی های جهان واقعی هستند که می توانیم با استفاده از اتر بخریم. در نتیجه، قرارداد توکن استاندارد که به ERC20 معروف است به طور قابل درکی پیچیده می باشد. به منظور سادگی، می توانیم آن را به صورت زیر خلاصه کنیم:
در اساس، یک قرارداد توکن تنها یک دفتر کل عمومی داخلی است. در حالی که دفتر کل عمومی خارجی موجودی های اکانت را به اتر ثبت می کند، یک قرارداد توکن موجودی های اکانت را به توکن ثبت می کند. ما می توانیم با استفاده از ساختار داده mapping به این امر دست یابیم (balanceof) که address به عنوان کلید و unit256 نیز به عنوان ارزش می باشد. به عنوان یک شی در این طرح، هر موجودی آدرس در balanceof با ۰ شروع خواهد شد.
از طریق روش constructor، ما می توانیم initialsupply توکن هایمان را به محض ایجاد قرارداد تنظیم کنیم. برای سادگی، ایجاد کننده قرارداد در ابتدا مالک همه توکن ها خواهد بود.
سپس توزیع توکن ها باید از ایجاد کننده قرارداد شروع شود. ما می توانیم با استفاده از روش transfer به این مهم دست یابیم که یک address و unit256 را می پذیرد که برای چه کسی و چه مقدار را تعیین می کند.
قبل از کاهش موجودی فرستنده و افزایش موجودی دریافت کننده توسط _value، ما می توانیم بررسی های require را اجرا کنیم تا اطمینان حاصل کنیم که فرستنده توکن های کافی دارد و موجودی جدید گیرنده سرریز نمی شود.
روی هم رفته قرارداد توکن ما به این شیوه می باشد:
برای کامپایل کردن قرارداد باید از solc استفاده کنیم که Truffle در ناحیه زیربنایی خود مورد استفاده قرار می دهد. اجازه دهید آن را به عنوان یک وابستگی نصب کنیم:
ما می خواهیم که اسکریپت compile.js ما با حذف هر چیز موجود در پوشه build شروع شود. به این ترتیب می توانیم تغییراتی را به قرارداد هایمان اعمال کنیم و در هر زمان آنها را دوباره کامپایل کنیم. به عبارت دیگر ما می خواهیم که از لحاظ برنامه نویسی:
۱- فولدر build و هر چیزی که درون آن است را حذف کنیم.
۲- یک فولدر build جدید را دوباره ایجاد نماییم.
۳- برای هر فایل Solidity در پوشه contracts، محتوای آن را به حافظه بخوانید.
۴- برای هر فایل Solidity در حافظه، آن را نیز به حافظه کامپایل کنید.
۵- برای هر فایل Solidity کامپایل شده، آن را در فولدر build با پسوند .json بنویسید.
برای ساخت مسیر هایی به فایل های ما، می توانیم از استاندارد path module استفاده کنیم. برای خواندن و نوشتن فایل ها، آسان تر است که از استاندارد fs-extra module بجای استاندارد fs module استفاده کرد. اجازه دهید آن را نصب کنیم:
بنابراین اسکریپت ما باید در solc، path و fs-extra مورد نیاز باشد:
برای ساخت مسیری به دایرکتوری build، می توانیم بنویسیم:
برای حذف دایرکتوری build و ایجاد یک دایرکتوری جدید در جایگاه آن، می توانیم بنویسیم:
برای ایجاد مسیری به قرارداد توکن ما، می توانیم بنویسیم:
برای خواندن محتوای آن تا حافظه، می توانیم بنویسیم:
برای کامپایل کردن محتوای آن می توانیم از کد زیر استفاده کنیم:
پرامتر دوم تعداد طبقه های قرارداد را که در فایل ما موجود هستند به solc اعلام می کند. در مورد ما، آن تنها طبقه Token می باشد. وقتی کامپایل شد مانند زیر به نظر می رسد:
توجه داشته باشید که آن یک شی json می باشد. ما تنها به :Token علاقه مندیم زیرا محتوی bytecode و interface است مه ما نیاز داریم. بنابراین برای نوشتن و کامپایل کردن به حافظه:
سرانجام برای نوشتن output به فولدر build به عنوان Token.json:
روی هم رفته، compile.js چنین می شود:
برای اجرای اسکریپت compile.js ما، می توانیم این فرمان را از روت پروژه اجرا کنیم.
این مرحله معادل اجرا می باشد:
حال ما آماده آزمایش قرارداد توکن خود هستیم.
تست Truffle
Truffle در زیربنای خود از چارچوب تست mocha به همراه chai برای اعلان ها استفاده می کند. اجازه دهید از mocha هم استفاده کنیم:
در اینجا به منظور سادگی تنها از کتابخانه استاندارد assert استفاده می کنیم.
برای اینکه قادر به امتحان قرارداد توکن باشیم، به یک شبکه محلی و روشی برای اتصال به یک کلاینت در حال اجرا مانند یک فراهم کننده نیاز داریم. ما می توانیم با استفاده از geth چنین چیزی را راه اندازی کنیم اما لازم است که هر تراکنش را به صورت دستی با Metamask امضا کنیم. برای انجام موفقیت آمیز این کار، می توانیم در عوض از ganache provider استفاده کنیم که با اکانت های خارجی در یک حالت قفل نشده می آید.
سپس ما به روشی برای تعامل با این فراهم کننده با استفاده از جاوا اسکریپت نیاز داریم. می توانیم برای انجام این کار از web3 استفاده کنیم.
از آنجا که پردازش تراکنش ها توسط شبکه زمان می برد، تقریبا هر اعلان web3 ناهمگام می باشد. نسخه 1.0 web3 اگرچه هنوز در مرحله آزمایش است اما promises را پشتیبانی می کند و بنابراین async و await را نیز پشتیبانی می کند که این کد را برای خواندن بسیار آسان تر می کند. اجازه دهید آن را نصب کنیم:
بنابراین اسکریپت تست ما برای شروع نیازمند assert، ganache-cli و web3 می باشد:
بنابراین ما می توانیم نمونه ای از web3 را با ganache.provider () به عنوان یک استدلال سازنده ایجاد کنیم:
حال می توانیم از این نمونه web3 برای معرفی قرارداد توکن خودمان استفاده کنیم. برای انجام این کار، ما به interface و bytecode از Token.json نیاز خواهیم داشت.
برای اتصال web3 با interface می توانیم بنویسیم:
برای گسترش bytecode می توانیم از روش deploy استفاده کنیم:
همچنین می توانیم استدلال های سازنده خود را به عنوان آرایه به arguments انتقال دهیم. برای ارسال واقعی تراکنش، می توانیم از روش send استفاده کنیم:
هر زمانی که بخواهیم حالت قرارداد را به روز رسانی کنیم، می توانیم از .send () استفاده کنیم. چنین اقداماتی همیشه هش تراکنش را برمی گرداند مگر اینکه تراکنش در حال گسترش باشد. پارامتر های موجود from، gas، gasprice و value هستند. آدرس مبدا (from) مورد نیاز است اما بقیه اختیاری هستند. gas و gasprice محدوده gas و هزینه واحد gas را تعیین می کنند. این در حالی است که value تعیین می کند که چه مقدار wei انتقال داده شود. اگر ما تنها می خواهیم که داده ها را بر روی قراردادمان بخوانیم، در عوض از .call () استفاده خواهیم کرد. خواندن داده ها همیشه رایگان است.
همچنان که دیدیم، ganache با ۱۰ اکانت خارجی می آید تا ما از آن استفاده کنیم. ما می توانیم از طریق زیر به آنها دسترسی داشته باشیم:
بنابراین برای گسترش یک نمونه تازه از قرارداد ما قبل از تست هر واحد، می توانیم تجهیزات تست زیر را مورد استفاده قرار دهیم:
در اینجا ما عمدا گسترش قرارداد را از اکانت خارجی اول یعنی [accounts0] انتخاب کردیم.
حال ما برای نوشتن تعدادی تست واحدی آماده ایم. برای شروع باید امتحان کنیم که قرارداد توکن ما در واقع به داخل شبکه محلی گسترش پیدا کرده باشد. هر زمانی که این گسترش و نصب موفقیت آمیز باشد، web3 یک نسخه از قرارداد را که مانند زیر است برمی گرداند:
توجه داشته باشید که ما می توانیم به همه متغیر ها و عملکرد های عمومی خود از طریق ویژگی method دسترسی داشته باشیم. توجه داشته باشید که همچنین در زیر options، ویژگی address محتوی آدرس نمونه قرارداد ما بر روی شبکه می باشد. می توانیم با استفاده از این حقیقت ادعا کنیم که address برای گذر از تست باید موجود باشد.
برای تست دوم ما، اجازه دهید تایید کنیم که خالق قرارداد براستی مالکیت همه توکن ها را به محض معرفی قرارداد دارا می باشد. از طریق ویژگی methods، می توانیم balanceof را فرا خوانده و آن را به [accounts0] انتقال دهیم.
برای تست سوم، اجازه دهید تایید کنیم که روش transfer ما در واقع آنچنان که ما قصد داریم عمل می کند.
برای انجام این انتقال، می توانیم روش transfer را از طریق ویژگی methods مانند زیر فرا خوانیم:
برای بررسی موفقیت انتقال، به دو فراخوان دیگر می پردازیم تا به موجودی جدید در [accounts0] و [accounts1] دست یابیم.
سپس می توانیم تایید کنیم که موجودی های آنها به درستی به روز رسانی شده اند:
برای آخرین تست، اجازه دهید تایید کنیم که فرستنده موجودی کافی دارد.
برای انجام این کار می توانیم از یک اظهار try/catch استفاده کنیم تا error را تصاحب کرده و ادعا کنیم که آن باید وجود داشته باشد:
با کنار هم قرار دادن همه تست های واحد خواهیم داشت:
برای اجرای تست های خود با npm، ما لازم است که mocha را به فایل package.json اضافه کنیم:
حال به راحتی می توانیم به اجرا بپردازیم:
این معادل اجرا می باشد:
با گذر از همه این تست ها، ما آماده گسترش قرارداد خود در یک شبکه اتریوم واقعی می باشیم.
Truffle Migrate
در خلال تست هایمان، ما از ganache provider برای گسترش قرارداد خود به شبکه محلی استفاده کردیم. به این ترتیب برای گسترش قرارداد خود به شبکه اتریوم عمومی، نیاز به یک فراهم کننده داریم که به یک نود اتریوم واقعی متصل شود. باید به طور محلی توسط geth یکی را راه اندازی کنیم اما به منظور سادگی ما در اینجا از Infura استفاده می کنیم. Infura که توسط ConsenSys پشتیبانی شده است پایانه هایی را فراهم می کند که ما می توانیم برای ایجاد آسان یک provider مورد استفاده قرار دهیم.
برای ایجاد یک فراهم کننده سفارشی، می توانیم از truffle-hdwallet-provider استفاده کنیم که Truffle در زیربنای خود از آن استفاده می کند:
Infura پایانه هایی را به شبکه اصلی و همچنین همه شبکه های تستی فراهم می کند. اجازه دهید از شبکه آزمایشی Rinkeby استفاده کنیم، بنابراین لازم نیست که پول واقعی خرج کنیم. همچنین اجازه دهید یک مخزن جدید در Metamask برای تست ایجاد کنیم. با استفاده از پایانه Rinkeby از Infura و عبارت سید از Metamask، می توانیم یک Provider مانند زیر ایجاد کنیم:
مانند قبل، می توانیم provider را به web3 انتقال دهیم تا به یک نمونه جدید دست یابیم:
با استفاده مجدد از منطق گسترش از کنترل تست خود با این نمونه web3، خواهیم داشت:
Metamask نیز در زیربنای خود از Infura استفاده می کند. به منظور ارجاع، ما address قرارداد را به کنسول (console) خروجی می دهیم. برای اجرای این اسکریپت مانند زیر عمل می کنیم:
قرارداد توکن ما حالا بر روی Rinkeby موجود می باشد.
این معادل اجرا می باشد:
در نهایت اجازه دهید یک فرانت اند درست کنیم که به قرارداد زنده ما متصل شود.
Truffle Unbox Drizzle
برای سازمان دادن کد boilerplate برای فرانت اند خود، می توانیم از Truffle برای برداشتن Drizzle استفاده کنیم.
Drizzle عالی است زیرا با انبارش Redux می آید که با حالت قرارداد ما همزمان می شود.
برای درک بهتر نحوه کارکرد این فرآیند، ما در عوض از create-react-app استفاده می کنیم که Drizzle در زیربنای خود مورد استفاده قرار می دهد. اجازه دهید آن را به صورت کلی نصب کنیم:
و سپس فرمان create-react-app را اجرا کنیم تا یک پروژه boilerplate بنام reactapp را ایجاد کنیم:
برای راه اندازی این اپلیکیشن، این فرمان را از روت (ریشه) پروژه اجرا می کنیم:
اجازه دهید مولفه App اصلی در src/App.js را به روز رسانی کنیم تا موجودی و شکلی برای انتقال توکن ها نشان داده شود:
ما می خواهیم کاربرانمان قادر باشند که از Metamask برای امضا و ارائه تراکنش ها به قرارداد در حال جریان ما استفاده کنند. ما می توانیم با بهره گرفتن از شی web3 که Metamask به داخل window گلوبال وارد می کند، به این امر دست یابیم:
این شی web3 با یک تامین کننده ساخته شده در داخل خود می آید که ما می توانیم مورد استفاده قرار دهیم:
اما توجه داشته باشید که این نسخه web3 0.20.3 می باشد. برای استفاده از نسخه ۱.۰ در عوض، باید آن را به عنوان وابستگی نصب کنیم:
سپس می توانیم از تامین کننده Metamask مانند زیر بهره بگیریم:
با استفاده از نسخه web3 خود، ما همچنین مطمئن می شویم که آن به طور ناگهانی تغییر پیدا نمی کند و کد ما را منحل نمی کند. Metamask به طور قابل درکی قصد دارد که ورود شی web3 را بعد از تشخیص اینکه اکثر برنامه های غیر متمرکز تنها تامین کننده آن را لازم دارند، متوقف کند. در آینده ای نزدیک، ethereumprovider در عوض وارد خواهد شد.
اجازه دهید نمونه web3 را برای استفاده فایل های دیگر استخراج کنیم:
برای متصل شدن به قرارداد در حال جریان ما، نیاز به ABI از فرآیند تلفیق خود و address قراردادمان بر روی Rinkeby داریم:
با کپی کردن بر روی ABI از Token.json و کپی کردن آدرس قرارداد از deploy.js در حال اجرا، می توانیم نمونه قرارداد در حال جریان خود را مانند زیر استخراج کنیم:
حال ما می توانیم نمونه token و web3 خود را برای استفاده مولفه App وارد کنیم:
در روش چرخه زندگی componentDidMount، ما می توانیم یک اعلان web3 ایجاد کنیم تا به همه اکانت های Metamask کاربر دست یابیم:
سپس ما می توانیم به فراخوانی قرارداد token خود برای دست یابی به balance اولین اکانت بپردازیم:
در Metamask، اکانت انتخاب شده در حال حاضر همیشه [accounts0] می باشد.
برای کمک به پردازش ارائه فرم، می توانیم یک روش helper ایجاد کنیم که عملکرد transfer قرارداد ما را فراخوانی کند. توجه داشته باشید که ما مجبور نیستیم که gas و gasprice را تعیین کنیم زیرا Metamask به طور خودکار بهترین ارزش ها را برای کاربر محاسبه می کند.
از آنجا که پردازش تراکنش ها در یک شبکه واقعی زمان می برد، ما باید یک پیام status برای مطلع کردن کاربر فراهم کنیم:
اجازه دهید به تست سطح کاربری بپردازیم. وقتی ما آن اکانت Metamask را انتخاب می کنیم که قرارداد توکن را ایجاد کرده است، موجودی درست ۱۰۰۰ را می بینیم که عرضه اولیه ماست.
انتخاب یک اکانت مختلف موجودی مناسب ۰ را نشان می دهد. حال اجازه دهید ۵۰ توکن را از اکانت ۱ به اکانت ۲ منتقل کنیم. وارد Metamask شوید و آدرس اکانت دوم را کپی کنید:
حال آن را به فیلد form انتقال دهید:
وقتی که ما بر Transfer کلیک می کنیم، Metamask تقاضای تایید می کند:
حتی اگر هیچ اتری فرستاده نشود، Metamask همیشه تقاضای تایید خواهد کرد زیرا تراکنش ها رایگان نیستند. بعد از اینکه شبکه تراکنش ما را می پذیرد، موجودی کاربر باید به روز شود:
اگر ما به اکانت ۲ در Metamask منتقل شویم، موجودی هم باید به روز رسانی شود:
با استفاده از txHash که برگردانده شد، ما می توانیم تراکنش خود را به طور آنی بر روی هر بلاک چین اکسپلورر مشاهده کنیم:
تست دستی سطح کاربری می تواند کاملا ملال آور باشد. برای کمک به خودکار سازی تست مولفه های React، create-react-app با چارچوب تست Jest ترکیب می شود. در واقع، Drizzle یک قدم بیشتر می رود و با Redux ترکیب می شود تا به ما یک store بدهد که با داده های قرارداد ما همزمان شود:
علاوه بر این، Drizzle به طور خودکار شکل هایی برای همه عملکرد های قرارداد ایجاد می کند که ما می توانیم آنها را به آسانی به reducers مناسب نقشه برداری کنیم.
از آنجا که reducers (تقلیل دهنده ها) اکثرا فراخوانی های غیر همگام web3 هستند، Drizzle همچنین با redux-saga ادغام می شود تا تست غیر همگام سریع انجام شود.
نتیجه گیری
آنچه که اتریوم را خاص می کند، تاکید آن بر لایه قرارداد هوشمند است که Truffle هم بر آن تاکید دارد و این یک چارچوب مناسب و یک قرارداد انتزاعی رده بالاتر را فراهم می کند که هر قرارداد Solidity را با عملکرد هایی برای کامپایل، تست، ارتقا و گسترش به طور همیشگی مزین می کند. این مقاله تحقیقی عمیق در مورد چنین ویژگی هایی بود و امید می رود که درک واضح تر و بهتری را موجب شود.
برای دریافت یک مثال واقعی زنده در مورد نحوه عملکرد Truffle که در یکی از پروژه های متن باز ما به کار رفته است، لینک زیر را مورد بررسی قرار دهید:
https://medium.com/heartbankstudio/how-to-decentralize-the-film-industry-f87bb26c91de?source=post_page—–f1ff6add416c———————-
در این مقاله ما تمامی کد متن برنامه غیر متمرکز خود را باز می کنیم و تمامی فرآیند فکری خود را از معماری گرفته تا سطح کاربری عملکردی کامل در کل بسته توسعه شرح می دهیم.