ERC-721
はじめに
このチュートリアルでは、Kaia Token Standards、特にNon-fungible Token Standard (ERC-721)に準拠したERC-721互換トークンの作成例を紹介します。
ERC-721 Non-Fungible Token Standardは、以下の3つのイベントと10のメソッドを定義している。 ERC-721のsupportsInterface
はERC-165 Standard Interface Detectionから派生したものであり、ERC-165はERC-721の一部である。
ERC-721互換トークンとは、以下のようにERC-721とERC-165のインターフェースを実装したトークンコントラクトです。 上記のインターフェイスに基づき、開発者は新しい機能やロジックを追加することでトークンをカスタマイズし、Kaiaネットワークにデプロイすることができる。
詳しくは公式のERC-721仕様書を参照。
ERC-721互換トークンとは、以下のようにERC-721とERC-165のインターフェースを実装したトークンコントラクトです。
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);function balanceOf(address _owner) external view returns (uint256);function ownerOf(uint256 _tokenId) external view returns (address);function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;function transferFrom(address _from, address _to, uint256 _tokenId) external payable;function approve(address _approved, uint256 _tokenId) external payable;function setApprovalForAll(address _operator, bool _approved) external;function getApproved(uint256 _tokenId) external view returns (address);function isApprovedForAll(address _owner, address _operator) external view returns (bool);function supportsInterface(bytes4 interfaceID) external view returns (bool);
上記のインターフェイスに基づき、開発者は新しい機能やロジックを追加することでトークンをカスタマイズし、Kaiaネットワークにデプロイすることができる。 詳しくは公式のERC-721仕様書を参照。
これらの関数は、指定されたトークンIDの所有権を別のアドレスに移す。 ERC-721が要求する safeTransferFrom
メソッドは2つあり、1つは data
付きで、もう1つは data
なしである。 どちらのメソッドも、data
を使わない方は data
に ""
をセットするだけであることを除けば、同じように動作する。 ERC-721の必須メソッドでもあるtransferFrom
よりもsafeTransferFrom
の方が好ましい。
このチュートリアルでは、MyERC721Card.sol
を実装する。このチュートリアルでは、MyERC721Card
はERC-721トークンである。
それぞれのMyERC721Card
は名前とレベルを持っている。例えば、"King "はレベル1、"Queen "はレベル1である。
MyERC721Card.sol
はOpenZeppelinのERC721実装に基づいている。 このチュートリアルのコードの大部分は、OpenZeppelin 2.3
からフォークされています。
1. ERC-721スマートコントラクトの作成
1.1 MyERC721Cardの全体構造
MyERC721Card.sol
の完全なソースコードを以下に示す。
pragma solidity ^0.5.0;// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/utils/Address.sol/** * @dev Collection of functions related to the address type, */library Address { /** * @dev Returns true if `account` is a contract. * * This test is non-exhaustive, and there may be false-negatives: during the * execution of a contract's constructor, its address will be reported as * not containing a contract. * * > It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. */ function isContract(address account) internal view returns (bool) { // This method relies in extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; }}// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/math/SafeMath.sol/** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "SafeMath: subtraction overflow"); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "SafeMath: modulo by zero"); return a % b; }}// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/drafts/Counters.sol/** * @title Counters * @author Matt Condon (@shrugs) * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number * of elements in a mapping, issuing ERC721 ids, or counting request ids. * * Include with `using Counters for Counters.Counter;` * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never * directly accessed. */library Counters { using SafeMath for uint256; struct Counter { // This variable should never be directly accessed by users of the library: interactions must be restricted to // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add // this feature: see https://github.com/ethereum/solidity/issues/4637 uint256 _value; // default: 0 } function current(Counter storage counter) internal view returns (uint256) { return counter._value; } function increment(Counter storage counter) internal { counter._value += 1; } function decrement(Counter storage counter) internal { counter._value = counter._value.sub(1); }}/** * @dev Interface of the ERC165 standard, as defined in the * [EIP](https://eips.ethereum.org/EIPS/eip-165). * * Implementers can declare support of contract interfaces, which can then be * queried by others (`ERC165Checker`). * * For an implementation, see `ERC165`. */interface IERC165 { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool);}/** * @dev Implementation of the `IERC165` interface. * * Contracts may inherit from this and call `_registerInterface` to declare * their support of an interface. */contract ERC165 is IERC165 { /* * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 */ bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; /** * @dev Mapping of interface ids to whether or not it's supported. */ mapping(bytes4 => bool) private _supportedInterfaces; constructor () internal { // Derived contracts need only register support for their own interfaces, // we register support for ERC165 itself here _registerInterface(_INTERFACE_ID_ERC165); } /** * @dev See `IERC165.supportsInterface`. * * Time complexity O(1), guaranteed to always use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool) { return _supportedInterfaces[interfaceId]; } /** * @dev Registers the contract as an implementer of the interface defined by * `interfaceId`. Support of the actual ERC165 interface is automatic and * registering its interface id is not required. * * See `IERC165.supportsInterface`. * * Requirements: * * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). */ function _registerInterface(bytes4 interfaceId) internal { require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); _supportedInterfaces[interfaceId] = true; }}/** * @dev Required interface of an ERC721 compliant contract. */contract IERC721 is IERC165 { 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); /** * @dev Returns the number of NFTs in `owner`'s account. */ function balanceOf(address owner) public view returns (uint256 balance); /** * @dev Returns the owner of the NFT specified by `tokenId`. */ function ownerOf(uint256 tokenId) public view returns (address owner); /** * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to * another (`to`). * * * * Requirements: * - `from`, `to` cannot be zero. * - `tokenId` must be owned by `from`. * - If the caller is not `from`, it must be have been allowed to move this * NFT by either `approve` or `setApproveForAll`. */ function safeTransferFrom(address from, address to, uint256 tokenId) public; /** * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to * another (`to`). * * Requirements: * - If the caller is not `from`, it must be approved to move this NFT by * either `approve` or `setApproveForAll`. */ function transferFrom(address from, address to, uint256 tokenId) public; function approve(address to, uint256 tokenId) public; function getApproved(uint256 tokenId) public view returns (address operator); function setApprovalForAll(address operator, bool _approved) public; function isApprovedForAll(address owner, address operator) public view returns (bool); function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;} // https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/token/ERC721/IERC721Receiver.sol/** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */contract IERC721Receiver { /** * @notice Handle the receipt of an NFT * @dev The ERC721 smart contract calls this function on the recipient * after a `safeTransfer`. This function MUST return the function selector, * otherwise the caller will revert the transaction. The selector to be * returned can be obtained as `this.onERC721Received.selector`. This * function MAY throw to revert and reject the transfer. * Note: the ERC721 contract address is always the message sender. * @param operator The address which called `safeTransferFrom` function * @param from The address which previously owned the token * @param tokenId The NFT identifier which is being transferred * @param data Additional data with no specified format * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` */ function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4);}// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.3.0/contracts/token/ERC721/ERC721.solcontract ERC721 is ERC165, IERC721 { using SafeMath for uint256; using Address for address; using Counters for Counters.Counter; // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; // Mapping from token ID to owner mapping (uint256 => address) private _tokenOwner; // Mapping from token ID to approved address mapping (uint256 => address) private _tokenApprovals; // Mapping from owner to number of owned token mapping (address => Counters.Counter) private _ownedTokensCount; // Mapping from owner to operator approvals mapping (address => mapping (address => bool)) private _operatorApprovals; /* * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde * * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ * 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd */ bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; constructor () public { // register the supported interfaces to conform to ERC721 via ERC165 _registerInterface(_INTERFACE_ID_ERC721); } /** * @dev Gets the balance of the specified address. * @param owner address to query the balance of * @return uint256 representing the amount owned by the passed address */ function balanceOf(address owner) public view returns (uint256) { require(owner != address(0), "ERC721: balance query for the zero address"); return _ownedTokensCount[owner].current(); } /** * @dev Gets the owner of the specified token ID. * @param tokenId uint256 ID of the token to query the owner of * @return address currently marked as the owner of the given token ID */ function ownerOf(uint256 tokenId) public view returns (address) { address owner = _tokenOwner[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } /** * @dev Approves another address to transfer the given token ID * The zero address indicates there is no approved address. * There can only be one approved address per token at a given time. * Can only be called by the token owner or an approved operator. * @param to address to be approved for the given token ID * @param tokenId uint256 ID of the token to be approved */ function approve(address to, uint256 tokenId) public { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _tokenApprovals[tokenId] = to; emit Approval(owner, to, tokenId); } /** * @dev Gets the approved address for a token ID, or zero if no address set * Reverts if the token ID does not exist. * @param tokenId uint256 ID of the token to query the approval of * @return address currently approved for the given token ID */ function getApproved(uint256 tokenId) public view returns (address) { require(_exists(tokenId), "ERC721: approved query for nonexistent token"); return _tokenApprovals[tokenId]; } /** * @dev Sets or unsets the approval of a given operator * An operator is allowed to transfer all tokens of the sender on their behalf. * @param to operator address to set the approval * @param approved representing the status of the approval to be set */ function setApprovalForAll(address to, bool approved) public { require(to != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][to] = approved; emit ApprovalForAll(msg.sender, to, approved); } /** * @dev Tells whether an operator is approved by a given owner. * @param owner owner address which you want to query the approval of * @param operator operator address which you want to query the approval of * @return bool whether the given operator is approved by the given owner */ function isApprovedForAll(address owner, address operator) public view returns (bool) { return _operatorApprovals[owner][operator]; } /** * @dev Transfers the ownership of a given token ID to another address. * Usage of this method is discouraged, use `safeTransferFrom` whenever possible. * Requires the msg.sender to be the owner, approved, or operator. * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function transferFrom(address from, address to, uint256 tokenId) public { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transferFrom(from, to, tokenId); } /** * @dev Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * Requires the msg.sender to be the owner, approved, or operator * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function safeTransferFrom(address from, address to, uint256 tokenId) public { safeTransferFrom(from, to, tokenId, ""); } /** * @dev Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * Requires the msg.sender to be the owner, approved, or operator * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred * @param _data bytes data to send along with a safe transfer check */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public { transferFrom(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } /** * @dev Returns whether the specified token exists. * @param tokenId uint256 ID of the token to query the existence of * @return bool whether the token exists */ function _exists(uint256 tokenId) internal view returns (bool) { address owner = _tokenOwner[tokenId]; return owner != address(0); } /** * @dev Returns whether the given spender can transfer a given token ID. * @param spender address of the spender to query * @param tokenId uint256 ID of the token to be transferred * @return bool whether the msg.sender is approved for the given token ID, * is an operator of the owner, or is the owner of the token */ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { require(_exists(tokenId), "ERC721: operator query for nonexistent token"); address owner = ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } /** * @dev Internal function to mint a new token. * Reverts if the given token ID already exists. * @param to The address that will own the minted token * @param tokenId uint256 ID of the token to be minted */ function _mint(address to, uint256 tokenId) internal { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _tokenOwner[tokenId] = to; _ownedTokensCount[to].increment(); emit Transfer(address(0), to, tokenId); } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * Deprecated, use _burn(uint256) instead. * @param owner owner of the token to burn * @param tokenId uint256 ID of the token being burned */ function _burn(address owner, uint256 tokenId) internal { require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own"); _clearApproval(tokenId); _ownedTokensCount[owner].decrement(); _tokenOwner[tokenId] = address(0); emit Transfer(owner, address(0), tokenId); } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * @param tokenId uint256 ID of the token being burned */ function _burn(uint256 tokenId) internal { _burn(ownerOf(tokenId), tokenId); } /** * @dev Internal function to transfer ownership of a given token ID to another address. * As opposed to transferFrom, this imposes no restrictions on msg.sender. * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function _transferFrom(address from, address to, uint256 tokenId) internal { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); _clearApproval(tokenId); _ownedTokensCount[from].decrement(); _ownedTokensCount[to].increment(); _tokenOwner[tokenId] = to; emit Transfer(from, to, tokenId); } /** * @dev Internal function to invoke `onERC721Received` on a target address. * The call is not executed if the target address is not a contract. * * This function is deprecated. * @param from address representing the previous owner of the given token ID * @param to target address that will receive the tokens * @param tokenId uint256 ID of the token to be transferred * @param _data bytes optional data to send along with the call * @return bool whether the call correctly returned the expected magic value */ function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool) { if (!to.isContract()) { return true; } bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data); return (retval == _ERC721_RECEIVED); } /** * @dev Private function to clear current approval of a given token ID. * @param tokenId uint256 ID of the token to be transferred */ function _clearApproval(uint256 tokenId) private { if (_tokenApprovals[tokenId] != address(0)) { _tokenApprovals[tokenId] = address(0); } }}contract MyERC721Card is ERC721{ struct Card { string name; // Name of the Card uint256 level; // Level of the Card } Card[] public cards; // First Item has Index 0 address public owner; constructor () public { owner = msg.sender; // owner of MyERC721Card contract who can create a new card } function mintCard(string memory name, address account) public { require(owner == msg.sender); // Only the Owner can create Items uint256 cardId = cards.length; // Unique card ID cards.push(Card(name, 1)); _mint(account, cardId); // Mint a new card }}
MyERC721Card.sol
は1つのインターフェース(IERC165
)、3つのライブラリ(Address
, SafeMath
and Counters
)、4つのコントラクト(ERC165
, IERC721
, IERC721Receiver
and MyERC721Card
)から構成される。
- IERC165\` インタフェースは、ERC-165 仕様書 に記述されているインタフェースを定義する。
Address
ライブラリはisContract
メソッドを定義し、account
が契約であるかどうかをテストする。SafeMath
ライブラリは、Solidityのuint256
型の安全な計算のためのオーバーフローチェックを追加した、Solidityの算術演算のラッパーを定義する。Counters
ライブラリは、1だけインクリメントまたはデクリメントできるカウンタを定義する。 これは、ERC721 IDを発行する際の要素数を追跡するために使用されます。- ERC165
は
IERC165\` インターフェースを実装している。 - IERC721\`はERC-721仕様に記述されているインターフェイスを定義しており、ERC-165も含まれている。
IERC721Receiver
はMyERC721Card
契約から使用されるonERC721Received
を定義する。- ERC721
は
IERC721と
ERC165\` を実装している。 MyERC721Card
は、ERC721
を使用して、名前とレベルを持つカード型の非可溶トークンを実装し、MyERC721Card
の契約所有者のみが新しいカードを作成することができます。
1.2 重要なメソッドを見てみよう
いくつかの重要な方法を詳しく見てみよう。
(1) constructor
of ERC721 and _INTERFACE_ID_ERC721
コンストラクタ constructor
は _INTERFACE_ID_ERC721
を登録する。_INTERFACE_ID_ERC721
は ERC-721 インターフェースに由来する 4 バイトのハッシュで、以下のようになる。
/* * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde * * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ * 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd */ bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; constructor () public { // register the supported interfaces to conform to ERC721 via ERC165 _registerInterface(_INTERFACE_ID_ERC721); }
登録後、ERC-721 と ERC-165 の supportsInterface
インターフェースが _INTERFACE_ID_ERC721
に対して呼び出されると true
を返し、このコントラクトが ERC-721 インターフェースを実装していることを示す。
(2) function balanceOf(address owner) public view returns (uint256 balance);
balanceOf
はERC-721の必須メソッドである。 balanceOf
はowner
の口座にあるNFTの数を返す。
function balanceOf(address owner) public view returns (uint256) { require(owner != address(0), "ERC721: balance query for the zero address"); return _ownedTokensCount[owner].current(); }
balanceOf
は _owner
が _TokensCount
に保持している Counter
オブジェクトから現在のカウントを返すだけである。
// Mapping from owner to number of owned token mapping (address => Counters.Counter) private _ownedTokensCount;
(3) safeTransferFrom
と transferFrom
これらの関数は、指定されたトークンIDの所有権を別のアドレスに移す。 ERC-721が要求する safeTransferFrom
メソッドは2つあり、1つは data
付きで、もう1つは data
なしである。 どちらのメソッドも、data
を使わない方は data
に ""
をセットするだけであることを除けば、同じように動作する。 ERC-721の必須メソッドでもあるtransferFrom
よりもsafeTransferFrom
の方が好ましい。
function safeTransferFrom(address from, address to, uint256 tokenId) public { safeTransferFrom(from, to, tokenId, ""); } function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public { transferFrom(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } function transferFrom(address from, address to, uint256 tokenId) public { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transferFrom(from, to, tokenId); }
safeTransferFrom
は to
アドレスがトークンを受け取れるかどうかをチェックする。 checkOnERC721Received
は検証ロジックを持つ。 もし to
のアドレスがコントラクトの場合、コントラクトは ERC-721 の onERC721Received
インターフェースを実装し、以下のように ERC-721 トークンを受信するための正しい 4 バイトのハッシュを返すべきである。
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool) { if (!to.isContract()) { return true; } bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data); return (retval == _ERC721_RECEIVED); }
実際に _transferFrom
は指定された tokenId
の所有権を以下のように転送する。
function _transferFrom(address from, address to, uint256 tokenId) internal { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); _clearApproval(tokenId); _ownedTokensCount[from].decrement(); _ownedTokensCount[to].increment(); _tokenOwner[tokenId] = to; emit Transfer(from, to, tokenId); }
(4) function _mint(address to, uint256 tokenId) internal
_mint
はERC-721の一部ではない。 _mint
はERC-721の一部ではない。 しかし、我々は新しいERC-721トークンを作成する方法が必要であり、この実装では以下のように新しいトークンを作成するために_mint
を導入しました。
function _mint(address to, uint256 tokenId) internal { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _tokenOwner[tokenId] = to; _ownedTokensCount[to].increment(); emit Transfer(address(0), to, tokenId); }
_mint
は内部メソッドであり、このコントラクトの内部で呼び出すことができる。 MyERC721Card.sol
では、_mint
は MyERC721Card
コントラクトの mintCard
メソッドからのみ呼び出される。 スマートコントラクトの所有者だけが mintCard
を呼び出すことができる。
function mintCard(string name, address account) public { require(owner == msg.sender); // Only the Owner can create Items uint256 cardId = cards.length; // Unique card ID cards.push(Card(name, 1)); _mint(account, cardId); // Mint a new card }
2. スマートコントラクトのデプロイ
RemixオンラインIDEまたはtruffleを使用して、上記のMyERC721Card
スマートコントラクトをデプロイすることができます。
2.1 Remix Online IDEを使ったスマートコントラクトのデプロイ
- Kaia Plugin for Remixにアクセスし、
MyERC721Card
契約を作成してください。 完全なソースコードはERC-721スマートコントラクトの作成にあります。 - 契約を展開するためのアカウントを作成します。
- まだアカウントをお持ちでない方は、https://toolkit.kaia.io/account/accountKeyLegacyからアカウントを作成してください。
- 蛇口からKAIAを試す - https://faucet.kaia.io
- 以下のように
MyERC721Card.sol
をデプロイしてみよう。
これでMyERC721Card
はライブになった! これでMyERC721Card
はライブになった! ERC-721互換の非可溶性トークンであるカードを鋳造し、転送することができます。
0x2645BA5Be42FfEe907ca8e9d88f6Ee6dAd8c1410
の口座にKing
とQueen
の2枚のカードを作成する。
これで2枚のカードが鋳造されたので、MyERC721Card
の非可菌トークンのステータスをチェックしてみよう。
balanceOf
はアカウント0x2645BA5Be42FfEe907ca8e9d88f6Ee6dAd8c1410
が2枚のカードを持っていることを示している。- パラメータ
1
を持つcards
は、トークン ID1
を持つMyERC721Card
がレベル 1 のQueen
であることを示す。 - パラメータ
0
を指定したownerOf
は、トークン ID0
を持つMyERC721Card
の所有者が0x2645BA5Be42FfEe907ca8e9d88f6Ee6dAd8c1410
であることを示しています。