ERC721トークンについて

#はじめに
イーサリアムブロックチェーンにおける、スマートコントラクトの初めての規格として2015年11月にERC20が誕生したあと、ERC20規格の欠点である誤送金問題などを解決するために誕生したのがERC223規格でした。その後、ERC20やERC223といった規格とは別方向に発展した全く新しいERC721トークンが誕生しました。
今回はそのERC721の特徴や仕様をまとめてみました

#ERC721の特徴
ERC20やERC223トークンは、通貨としての役割を持つため代替可能トークンとして使われていました。
通貨は「価値の保存」、「価値の尺度」、「交換の手段」を持っています。なので、例えば、アリスが持っている100円とボブが持っている100円は同等のものなので、代替可能であると言えます。一方、ERC721は、ERC20やERC233トークンのように、どのトークンに対しても同じ価値を持つのではなく、**固有の希少性や独自性を持つことができる代替不可能トークン**です
この代替不可能トークンのことを**NFT(Non-Fungible Token)**といいます。各トークンがその所有者の名前などのメタデータを含むことで、トークンごとに違う価値を生み出せます。
例えば、あるゲーム内でモンスターを交換する場合、それぞれモンスターによってレベルやレア度は違うので、ERC721トークンによって固有の価値を表し、取引が行えます。

#ERC721の関数とイベント

ERC721トークンは、提案されたERC165インターフェイスを実装する必要があります。
この標準により、コントラクトによって実装されたインターフェイスの検出が可能になります。これは、トークンが実装するインターフェイスを検出し、その結果、メソッド/コードを適応させてそれとやり取りできるため、非常に便利です。

- ERC721のコントラクト

| function       |       return |
|:-----------------|:------------------|
|balanceOf(address owner)|uint256|
|ownerOf(uint256 tokenId)|address|
|approve(address to, uint256 tokenId)||
|_exists(uint256 tokenId)|bool|
|getApproved(uint256 tokenId)|address|
|setApprovalForAll(address to, bool approved)||
|isApprovedForAll(address owner, address operator)|bool|
|_isApprovedOrOwner(address spender, uint256 tokenId)|bool|
|safeTransferFrom(address from, address to, uint256 tokenId)||
|safeTransferFrom(address from, address to, uint256 tokenId, bytes data)||
_safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)||
|transferFrom(address from, address to, uint256 tokenId)||
|_transferFrom(address from, address to, uint256 tokenId)||
|_mint(address to, uint256 tokenId)||
|_safeMint(address to, uint256 tokenId)||
|_safeMint(address to, uint256 tokenId, bytes memory _data)||
|_burn(uint256 tokenId)||
|_burn(address owner, uint256 tokenId)||
|_checkOnERC721Received(address from, address to, uint256 tokenId, bytes _data)|bool|
|_clearApproval(uint256 tokenId)||

- ERC165のインターファイス

|function | return | 
|:-----------------|:------------------|
|supportsInterface(bytes4 interfaceID)|bool|

上記のリストを見てみると、同じ名前の関数がいくつか定義されているのですが、引数によって実装が違うみたいです。

####・balanceOf
ownerが持つNFTの総量を返します
####・ownerOf
tokenIdからそのownerのアドレスを返す
####・approve
指定されたトークンIDを転送するためにtoを承認します

```text
function approve(address to, uint256 tokenId) public { 
    address owner = ownerOf(tokenId); 
    require(to != owner, "ERC721: approval to current owner"); 

    require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()), "ERC721: approve caller is not owner nor approved for all"); 

    _tokenApprovals[tokenId] = to; 
    emit Approval(owner, to, tokenId); 
     } 
```

`_msgSender()`がトークンのオーナーであることを確認し、トークンIDに対して承認されるアドレスtoを指定します。

####・_exists
指定されたトークンが存在するかどうかを返す
####・getApproved
tokenIdからapproveしているアドレスを返す
####・setApprovalForAll
toは承認を設定するオペレーター、approvedは設定する承認のステータスです。
オペレーターは、送信者のすべてのトークンを代理で転送できます。
####・isApprovedForAll
`setApprovalForAll`で_operatorが_ownerによって承認されているかどうかを示します
####・_isApprovedOrOwner
spenderがtokenIdを転送できるかどうかを返す

```text
return(spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender))
```

spenderがオーナーか、トークンがspenderによって承認されてるか、`isApprovedForAll`でspenderがtokenIdを転送できるかどうかを返します

####・safeTransferFrom(引数にdataを持たない)

```text:
function safeTransferFrom(address from, address to, uint256 tokenId) public {
    safeTransferFrom(from, to, tokenId, ""); 
} 
```
第四引数のdataを""として`safeTransferFrom()`呼び出しています。

####・safeTransferFrom(引数にdataを持つ)

```text:
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public { 
    require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 
    _safeTransferFrom(from, to, tokenId, _data); 
} 
```

`_isApprovedOrOwner`でsenderがtokenIdを転送できるかどうかを確認し、`_safeTransferFrom`を呼びます

####・_safeTransferFrom

```text:
function _safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) internal { 
    _transferFrom(from, to, tokenId); 
    require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); 
} 
```

`_transferForm`でNFTのownerをfromからtoへ移します.
requireで`_checkOnERC721Received`を呼びます。

`_checkOnERC721Received`では、
toがコントラクトアドレスではないとき(ユーザーアドレスのとき)、この関数はtrueを返します。
もし、toがコントラクトアドレスのとき、`ERC721Receiver`に従って`onERC721Received()`を呼びます。これはbyte4で返し、`_ERC721_RECEIVED`と等しいかどうか確認する。

`_ERC721_RECEIVED`は`bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;`
と定義されています。

また、これは`bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`と等しいようです。

`_checkOnERC721Received`の実装は以下のようになっています。

```text

function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool) { 
    if (!to.isContract()) { 
        return true; 
    } 
  
    bytes4 retval = IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data); 
    return (retval == _ERC721_RECEIVED); 
} 
```
####transferFrom

```text
function transferFrom(address from, address to, uint256 tokenId) public { 
    require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 
    _transferFrom(from, to, tokenId); 
     } 
```

`_isApprovedOrOwner`で`_msgSender()`がそのトークンを転送できるかどうか確認し、
`_transferFrom`を呼びます
msg.senderが所有者、承認者、またはオペレーターである必要があります。

####・_transferFrom

```text
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);
}

```
`transferFrom()`とは対照的に、msg.senderに制限を課しません。
ここでNFTのownerをfromからtoへ移す処理を実装する。
####・_mint
mintとは、「鋳る」という意味であり、トークンやコインを作成します。

```text
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); 
} 
```
toは作成されたトークンを所有するアドレスを示すため、ゼロアドレスではないか確認する必要があります。また、トークンがすでに存在しているかも確認します。

requireが通ったらトークンに所有オーナーを設定し、トークン数をインクリメントします。

####・_safeMint(引数にdata)を持たない

```text:
function _safeMint(address to, uint256 tokenId) internal { 
    _safeMint(to, tokenId, "");
} 
```
第三引数のdataを""として`\_safeMint()`を呼び出します

####・_safeMint(引数にdataを持つ)

```
function _safeMint(address to, uint256 tokenId, bytes memory _data) internal {
    _mint(to, tokenId);
    require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
    }
```
`_mint()`にto, tokenIdを与え、呼び出します。
`_safeTransferFrom()`の時と同様に`_checkOnERC721Received()`をrequireします

####・_burn(引数にaddressを持たない)

`_mint`とは反対にトークンを削除する関数です

```text
function _burn(uint256 tokenId) internal { 
    _burn(ownerOf(tokenId), tokenId); 
} 
```
tokenIdの所有アドレスとtokenIdを与え、\_burnを呼びます

####・_burn(引数にaddressを持つ)

```
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); 
} 
```
`_clearApproval`でtokenIdの承認をクリアします。
オーナーのトークン数をデクリメントし、トークンの所有者をゼロアドレスにします。

---

ERC165は、スマートコントラクトがどんなインターフェイスを実装しているかを公開するインターファイスです。

####・supportsInterface

supportsInterface(interfaceID)で、使用可能なインターフェースを実装しているかどうかを判断します。

#ERC721が使用されているCryptoKittieについて

CryptoKittieとはイーサリアムのブロックチェーン上で、猫を集めたり、売買したり、交換するゲームです。
このようなブロックチェーンの技術を使ったゲームをDapps(Decentralized Applications)と呼び、非中央集権の分散型アプリケーションのことを指します。
他にもイーサエモンやBitPetなどといったゲームも、育成し、トレードや売買が可能となっています。
これらのゲームではERC721規格を用いており、トークンそれぞれに独自性や希少性を持たせることができます。
分散型アプリケーションの利点である非中央集権で取引データが管理されるため、取引のに透明性があり、仮想通貨を使って安全に取引が行えます。

#まとめ
ERC20やERC223のような代替可能トークンとは反対に、代替不可能なERC721を簡単にまとめました。ERC721規格を用いたゲームは今後もっと増えてくると思うし、新たなビジネスでもっとNFTが活用されると思いました。