انجمن توسعه قرارداد هوشمند

چگونه توکن erc-721 بسازیم؟

چگونه توکن ERC-721 بسازیم؟

نوشته شده توسط @sataso

در این نوشتار درباره توکن ERC-721 و طریقه ساخت یک نمونه از این توکن توضیح می‌دهیم.

اتریوم یکی از بلاکچین‌های شناخته شده بر اساس قراردادهای هوشمندی به نام توکن است که قابلیت خرید، فروش و یا مبادله را دارا هستند. استانداردهایی برای ساخت این توکن‌ها تعریف شده‌است که شامل قوانینی هستند که توکن‌ها ملزم به پیروی از آن‌ها می‌باشند.

استاندارد ERC-721 چیست؟

ERC-721 یک استاندارد برای ساخت توکن در بلاکچین اتریوم می‌باشد. بر خلاف استاندارد ERC-20 که برای ساخت توکن‌های تعویض‌پذیر (Fungible) استفاده می‌شود. ERC-721 به شما قابلیت ایجاد توکن‌های غیر قابل‌تعویض (Non-Fungible Tokens یا به اختصار NFT) را در اتریوم می دهد.

توکن‌های تعویض‌پذیر به توکن‌هایی گفته می‌شوند که از نظر ارزش و ویژگی‌ها با هم برابر هستند در نتیجه می‌توانیم آن‌ها را با هم معاوضه کرده و همچنین با اضافه و کم کردن آن‌ها به/از هم، مقدار آن‌ها را تغییر دهیم. در مقابل، توکن‌های غیرقابل‌تعویض دارای ویژگی های منحصر به فرد هستند. در نتیجه توکن‌های غیرقابل‌تعویض در یک قرارداد هوشمند دارای ارزش متفاوت و به همین دلیل غیر قابل معاوضه، غیرقابل تجمیع و یا کم کردن هستند. به عنوان مثال، اگر بخواهیم جهت خرید و فروش املاک یک منطقه از قراردادهای هوشمند اتریوم استفاده کنیم، به دلیل اینکه هر یک از املاک دارای موقعیت جغرافیایی، ارزش و به طور کل ویژگی‌های متفاوتی هستند، از توکن‌های غیرقابل‌نعویض استفاده می‌کنیم. پروژه‌های زیادی از این استاندارد جهت پیاده‌سازی ایده‌های تجاری بر بستر بلاکچین اتریوم استفاده کرده‌اند، از جمله: پروژه Axie Infinity که یک دنیای دیجیتالی حیوانات است که با مشارکت و بازی در این اکوسیستم، بازیکنان میتوانند مبارزه کنند، امتیاز جمع کنند و یک قلمرو برای این حیوانات بسازند.

https://lh6.googleusercontent.com/lGDmPLmMZshTGNvhc541hC25mL18I2c0Bc08U3Bt7e0DSkI7R8ll83DlQObuUeeXBJ3KLi6_gcnS7wBuAbZHS-ocRkPmOIma54CnQ5kWNzNjn65WJE-jjO8zdZrTi5WeKrPW356V

از دیگر پروژه های موفق در این زمینه بازی CryptoKitties است که به کاربران اجازه خرید، جمع‌آوری، تولید نسل‌های جدید و فروش گربه‌های مجازی را می‌دهد. لیست توکن‌های ایجاد شده بر اساس استاندارد ERC-721 را می‌توانید در این لینک ببینید.

https://lh5.googleusercontent.com/RBhahOLDsWp8OL-e-EROQfqf5oEOXa9ayOlCdYH5S_J9ZyoC3zXTkD775PxSliAotvEvgTgdQBeB7p1X2-lZZsPkCvwiJ7VH7ndMnz-piLLV3D8l4m_3ZZYfgSo1hFCH1_YR_sdN


توابع و رویدادهای استاندارد ERC-721

به طور کلی استاندارد ERC-721 دارای 9 تابع خارجی (external) و 3 رویداد برای ارتباط با توکن‌های غیرقابل‌تعویض می‌باشد. اگر یک قرارداد هوشمند این توابع و رویدادها را پیاده‌سازی کند یک قرارداد توکن غیرقابل‌تعویض ERC-721 نامیده می‌شود و به محض اجرا، این قرارداد مسئول نگهداری توکن‌های حاصل در اتریوم می‌باشد.

توکن‌ها در این استاندارد در ساختار داده‌ای mapping ذخیره می‌شوند. این ساختار داده‌ای یک کلید یکتا را به مقدار آن نگاشت می‌کند. این mappingها عبارتند از:

mapping (uint256 => address) private _tokenOwner;

در tokenOwner_ هر توکن به مالک آن نگاشت شده‌است.

mapping (uint256 => address) private _tokenApprovals;

در tokenApprovals_ هر توکن به آدرس تائید شده توسط مالک توکن جهت انتقال توکن نگاشت شده‌است.

mapping (address => uint256) private _ownedTokensCount;

در ownedTokensCount_ هر آدرس به تعداد توکن‌هایی که متعلق به آن است، جهت جلوگیری از ایجاد حلقه برای شمارش توکن‌های هر آدرس، نگاشت شده است.

mapping (address => mapping (address => bool)) private _operatorApprovals;

در operatorApprovals_ هر مالک توکن به اپراتورهای مختلف (مانند کیف پول‌های مجازی یا صرافی‌ها) نگاشت شده است و نشان می‌دهد آیا اپراتور توسط مالک توکن تائید شده است یا خیر.

رویدادها

این رویدادها در زمان انتقال یا تاییدها صادر می‌شوند.

event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

توابع

در زیر توابع قرارداد 721 ERC-، به همراه بدنه تابع و مقادیر ورودی و خروجی را بررسی می‌کنیم:

  • balanceOf : این تابع به عنوان ورودی یک آدرس می‌گیرد و در ابتدا، این تابع معتبر بودن آدرس را بررسی میکند. سپس موجودی آن آدرس را برمی‌گرداند.
function balanceOf(address owner) public view returns (uint256) {

require(owner != address(0));

return _ownedTokensCount[owner];

}
  • :ownerOf آدرس صاحب توکن را برمی گرداند.
function ownerOf(uint256 tokenId) public view returns (address) {

address owner = _tokenOwner[tokenId];

require(owner != address(0));

return owner;

}
  • transferFrom: جهت انتقال توکن از یک آدرس به آدرس دیگر (استفاده از این تابع توصیه نمی‌شود). زیرا مسئولیت انتقال به گیرنده صحیح بر عهده فرستنده است. در این تابع ابتدا چک می‌شود که آیا فرستنده مالک توکن یا دارای اجازه برای انتقال توکن هست یا خیر. همچنین وجود آدرس مقصد نیز بررسی می‌شود. سپس مالک توکن و تعداد توکن‌های مالک حال حاضر و قبلی و همچنین اجازه‌های داده شده به آدرس‌های دیگر جهت انتقال توکن به‌روزرسانی می‌شود.

function transferFrom(address from, address to, uint256 tokenId) public {

require(_isApprovedOrOwner(msg.sender, tokenId));

require(to != address(0));

_clearApproval(from, tokenId);

_removeTokenFrom(from, tokenId);

_addTokenTo(to, tokenId);

emit Transfer(from, to, tokenId);

}
  • safeTransferFrom : جهت انتقال توکن از یک آدس به آدرس دیگر. در این تابع، پس از انتقال توکن، جهت جلوگیری از گم‌شدن و از دست‌دادن توکن، گیرنده را از جهت توانایی دریافت توکن بررسی می‌کند. دو تابع با این نام و با پارامترهای ورودی متفاوت در این استاندارد وجود دارد.
function safeTransferFrom(address from, address to, uint256 tokenId, bytes _data) public

{

transferFrom(from, to, tokenId);

require(_checkOnERC721Received(from, to, tokenId, _data));

}
  • Approve: جهت تایید آدرس دیگر برای ارسال تراکنش از صاحب توکن (و یا از طرف صاحب توکن) به اکانت دیگر.
function approve(address to, uint256 tokenId) public {

address owner = ownerOf(tokenId);

require(to != owner);

require(msg.sender == owner || isApprovedForAll(owner, msg.sender));

_tokenApprovals[tokenId] = to;

emit Approval(owner, to, tokenId);

}
  • setApprovalForAll: تائید یا عدم تائید یک اپراتور جهت داشتن اجازه ارسال همه توکن‌ها از آدرس مالک توکن‌ها به یک آدرس دیگر.

function setApprovalForAll(address to, bool approved) public{

require(to != msg.sender);

_operatorApprovals[msg.sender][to] = approved;

emit ApprovalForAll(msg.sender, to, approved);

}
  • IsApprovedForAl: مقدار صحیح در صورتی برگردانده می‌شود که آدرس اپراتور مورد نظر توسط مالک مورد نظر تائید شده‌باشد.
function isApprovedForAll(address owner, address operator) public view returns (bool){

return _operatorApprovals[owner][operator];

}
  • GetApproved: آدرسی را برمی‌گرداند که اجازه انتقال توکن‌ها برای صاحبان توکن‌ها را دارد. اگر آدرسی تنظیم نشده باشد 0 برمی‌گرداند.

function getApproved(uint256 tokenId) public view returns (address){

require(_exists(tokenId));

return _tokenApprovals[tokenId];

}

ساخت یک نمونه قرارداد توکن ERC-721 با استفاده از truffle

برای ساخت این توکن از کتابخانه OpenZeppelin استفاده می‌کنیم.


truffle init

npm install openzeppelin-solidity

در ابتدا یک قرارداد هوشمند ایجاد می‌کنیم که از قرارداد هوشمند ERC721 ارث‌بری می‌کند. به این ترتیب قرارداد ما تمام خواص قرارداد والد را دارا میباشد. به مثال زیر توجه کنید:

pragma solidity >=0.6.0 <0.8.0

import @"openzeppelin/contracts/token/ERC721/ERC721.sol";

import @"openzeppelin/contracts/utils/Counters.sol";

contract SampleErc721 is ERC721{

using Counters for Counters.Counter;

Counters.Counter private _tokenIds;

constructor() public ERC721("Sample", "ERC"){}

function mint(address user) public returns (uint256){

_tokenIds.increment();

uint256 newTokenId = _tokenIds.current();

_mint(user, newTokenId);

return newTokenId;

}

}

در کد بالا از قرارداد هوشمند Counters.sol جهت ایجاد شناسه‌های توکن یکتا استفاده شده‌است. در تابع سازنده ERC721، به دو ورودی نیاز است. ورودی اول نام توکن و ورودی دوم نماد انتخابی برای توکن است. همچنین برای اختصاص توکن به یک کاربر در داخل تابع mint پس از ایجاد یک شناسه توکن جدید از فراخوانی تابع

_ mint(address user, uint256 tokenId) که از توابع داخلی قرارداد ERC721 می‌باشد، استفاده شده‌است.

تست قرارداد

برای تست قرارداد و استفاده از محیط کنسول Truffle، دستور truffle develop را وارد می‌کنیم. بدین ترتیب یک محیط توسعه بلاکچین در اختیار ما قرار می‌گیرد. ‌با استفاده از دستور truffle compile قراردادهای ایجاد شده را از نظر وجود خطا بررسی می‌کنیم.

با استفاده از دستور truffle migrate قرارداد هوشمند خود را بر روی بلاکچین ایجاد شده توسط truffle، قرار می‌دهیم. فایل migration دارای اطلاعات لازم جهت استقرار قرارداد هوشمند است:

const SampleErc721 = artifacts.require("SampleErc721");

module.exports = function (deployer){

deployer.deploy(SampleErc721);

};

!
migration

حال می‌توانیم از قرارداد موجود بر روی بلاکچین محلی برای تست قرارداد استفاده کنیم. برای این منظور از دستور زیر برای ایجاد یک نمونه از قرارداد استفاده می‌کنیم:

nft = await SampleErc721.deployed()

جهت تست تابع mint‌ در قرارداد از یکی از ۱۰ آدرسی که در هنگام راه اندازی حالت توسعه ترافل در اختیار ما قرار می‌گیرد استفاده می‌کنیم. به این ترتیب با فراخوانی تابع موجود در قرارداد همانطور که در شکل زیر می‌بینید به آدرس مورد نظر توکن اختصاص داده‌میشود:

!
test
شما می‌توانید توابع دیگری را به قرارداد اضافه کنید و با روش بالا آنها را تست کنید.

نتیجه‌گیری

در این نوشتار درباره توکن ERC-721 و روش ایجاد آن با استفاده از ترافل در اتریوم صحبت کردیم. از این توکن در موارد بسیار زیادی می‌توان استفاده کرد. از جمله جهت ثبت املاک، اسناد، افراد، اثرهای هنری، اختراعات و موارد بسیار دیگر. شما می‌توانید ایده‌های خود که به توکن‌های غیرقابل‌تعویض نیاز دارند را با استفاده از قابلیتهای ارائه شده توسط بلاکچین و قراردادهای هوشمند به پروژه‌های عملی تبدیل کنید.

7 Likes

ببخشید ی سوال داستم
کیورد virtual در تابع _isApprovedOrOwner چه کاربردی داره؟

سلام
به طور کلی فانکشنی که virtual باشه به این معناست که به کانترکت هایی که ازش inheritate یا ارث بری میشه اجازه override رفتارش رو میده.

1 Likes