ERC-721トークンをOpenZeppelinで作ってみる

ERC721に対応したトークンをOpenZeppelinで作り、テストネットにデプロイしてみました。フロントエンドも実装したので、Dappsブラウザを使える方なら誰でもERC721トークンの譲渡を試すことができます。

![localhost_8000_.png](https://qiita-image-store.s3.amazonaws.com/0/154409/f11867b3-4079-d174-8a46-d5f986849091.png)

開発したアプリには上記のURLからアクセスできます。Ropstenネットワークにのみデプロイしており、閲覧にはMistやMetaMaskなどのDappsブラウザが必要です。常識的な範囲内ならコントラクトを自由に動かしてもらって構いません。ブロックチェーンが更新されてもアプリは自動更新されずページの再読み込みが必要なのであしからず…

https://arosh.github.io/erc721-sandbox/

## ERC721とは

ERC20のような、Ethereumブロックチェーン上のトークンの標準規格のひとつです。仕様のドラフトは以下のページに記載されています。

[ERC\-721 Non\-Fungible Token Standard \| Ethereum Improvement Proposals](https://eips.ethereum.org/EIPS/eip-721)

ERC721の目的はNon-Fungible Token (NFT) の規格を標準化することです。NFTの概念の説明や利用用途については以下のページが詳しいです。

* [ERC721とは？ – ブロックチェーン総合研究所【BRI】 – Medium](https://medium.com/blockchain-research-institute/erc721%E3%81%A8%E3%81%AF-38e80ea2b328)
* [仮想子猫経済を支える ERC721 · m0t0k1ch1st0ry](https://m0t0k1ch1st0ry.com/blog/2017/12/19/erc721/)

## ERC721トークンを作り方

[仕様書](https://eips.ethereum.org/EIPS/eip-721) に書いてあるとおりのメソッドを実装すればトークンを作ることができるわけですが、実装しなければならないメソッドがたくさんあって脆弱性なく実装できる気がしないのでライブラリを利用することを推奨します。ERC20トークンを作るときにも利用されている [zeppelin-solidity](https://github.com/OpenZeppelin/openzeppelin-solidity) には、ERC721トークンのベースとなるコードが追加されています。

↓ERC721関連のコードはここにあります。

https://github.com/OpenZeppelin/openzeppelin-solidity/tree/master/contracts/token/ERC721

このディレクトリにはファイルがたくさんあって混乱するのでひとつずつ解説します。

### ERC721Basic

`transfer` や `approve`, `ownerOf` といったトークンの譲渡や所有に関するメソッドのインターフェースが定義されています。インターフェースの定義だけで実装はありません。

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/4a10f727c4756a8f6433f272a49e7a15db5e4b8f/contracts/token/ERC721/ERC721Basic.sol

### ERC721BasicToken

ERC721Basicで定義されたインターフェースを実装したものです。modifierとして `onlyOwnerOf(_tokenId)`（トークンのオーナーだけこのメソッドを呼べる）や `canTransfer(_tokenId)`（トークンのオーナー or approveされた人だけこのメソッドを呼べる）といったものが定義されているので、トークンに独自の機能を追加するときに活用できると思います。

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/4a10f727c4756a8f6433f272a49e7a15db5e4b8f/contracts/token/ERC721/ERC721BasicToken.sol

### ERC721

ERC721Basicに追加のインターフェースとしてERC721EnumerableとERC721Metadataを追加したものです。インターフェースの定義だけで実装はありません。

ERC721EnumerableはERC721の中で **enumeration extension** と呼ばれている追加機能のインターフェースです。これは発行されたトークンを列挙するための機能で、`totalSupply()` と `tokenByIndex(_index)` を組み合わせることで、コントラクトから発行された有効なトークンを列挙することができます。また `tokenOfOwnerByIndex(_index)` をERC721Basicの `balanceOf()` と組み合わせれば、あるユーザーが所有するトークンを列挙することもできます。

ERC721MetadataはERC721の中で **metadata extension** と呼ばれている追加機能のインターフェースです。ERC20にも同様の機能がある `name()`, `symbol()` に加えて、発行された個別のトークンに紐付けられたメタデータの場所を返すメソッドである `tokenURI(_tokenId)` のインターフェースが定義されています。トークンがNFTとして機能するためには `tokenURI` などを使ってメタデータを管理する必要があります。

これらの機能は必須なように思えますが、実装するためにはトークンのリストやメタデータをブロックチェーン上に格納する必要があるため、コントラクトの実行が高価になってしまう恐れがあります。そのためこれらの機能はoptionalとなっています。

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/4a10f727c4756a8f6433f272a49e7a15db5e4b8f/contracts/token/ERC721/ERC721.sol

### ERC721Token

ERC721で定義されたインターフェースを実装したものです。さらに、ERC721BasicToken（ERC721Basicで定義されたインターフェースを実装したもの）も継承しています。

このコントラクトをデプロイすればERC721トークンが完成するかというとそういうわけではなく、トークンを生成する `_mint(_to, _tokenId)`、トークンを償却する `_burn(_owner, _tokenId)`、トークンのメタデータを書き換える `_setTokenURI(_tokenId, _uri)` はinternalなメソッドとなっているため、このままコントラクトをデプロイしてもトークンを生成することができません。ERC721Tokenを継承したコントラクトを作成し、`_mint`, `_burn`, `_setTokenURI` を用いて、作成したいトークンの特性に合わせた `mint`, `burn`, `setTokenURI` を実装する必要があります。

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/4a10f727c4756a8f6433f272a49e7a15db5e4b8f/contracts/token/ERC721/ERC721Token.sol

### ERC721Receiver

ERC721ではトークンが行方不明になってしまうことを防ぐため、コントラクトがトークンを受け取る場合にはコントラクトが `onERC721Received(_from, _tokenId, _data)` を呼びだされた時に受け取りを許可するならば戻り値としてマジックナンバー 0xf0b9e5ba を返すことを要求しています。このコントラクトでは `onERC721Received` のインターフェースとマジックナンバー 0xf0b9e5ba の定義のみを行っています。

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/4a10f727c4756a8f6433f272a49e7a15db5e4b8f/contracts/token/ERC721/ERC721Receiver.sol

### ERC721Holder

ERC721Receiverで定義された `onERC721Received` の実装例として、何も判定を行わずにマジックナンバー 0xf0b9e5ba を返す処理を定義しています。

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/4a10f727c4756a8f6433f272a49e7a15db5e4b8f/contracts/token/ERC721/ERC721Holder.sol

### DeprecatedERC721

ERC721の議論中に名前が変更されたり削除されたメソッドのインターフェースを定義しています。

https://github.com/OpenZeppelin/openzeppelin-solidity/blob/4a10f727c4756a8f6433f272a49e7a15db5e4b8f/contracts/token/ERC721/DeprecatedERC721.sol

## 実際にERC721トークンを作ってみる

今回はサンプルとして、誰でもトークンを発行できて、所有者はいつでも自分のトークンのメタデータを書き換えたりトークンを償却したりすることができるコントラクトを作ってみました。

トークンはERC721Tokenを継承して `mint`, `setTokenURI`, `burn` を実装することによって行います。ERC721BasicTokenで定義されている `onlyOwnerOf` も使ってみました。

```solidity:MyToken.sol
pragma solidity ^0.4.23;

import "zeppelin-solidity/contracts/token/ERC721/ERC721Token.sol";

contract MyToken is ERC721Token {
  uint256 internal nextTokenId = 0;
  
  constructor() public ERC721Token("MyToken", "MTKN") {}

  function mint() external {
    uint256 tokenId = nextTokenId;
    nextTokenId = nextTokenId.add(1);
    super._mint(msg.sender, tokenId);
  }

  function setTokenURI(uint256 _tokenId, string _message) external onlyOwnerOf(_tokenId) {
    super._setTokenURI(_tokenId, _message);
  }

  function burn(uint256 _tokenId) external onlyOwnerOf(_tokenId) {
    super._burn(msg.sender, _tokenId);
  }
}
```

再掲しますが、このコントラクトをRopstenにデプロイしてフロントエンドを実装したものが以下のものになります。

https://arosh.github.io/erc721-sandbox/

mintのボタンを押すと、押した人にトークンが発行されます。burnはトークンの償却を行うボタンです。setTokenURIはメタデータのURIを設定するボタンです。参照先のURIにはERC721 Metadata JSON Schemaとして以下のスキーマに従うJSONオブジェクトが配置されていることが期待されています。

```json
{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents",
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents",
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive.",
        }
    }
}
```

今回は、メタデータを置くサーバーを用意したり、Swarmの処理を書いたりするのが面倒だったので、tokenURIとしてメタデータを配置しているURIではなく何らかのメッセージを書き込めるようにしてみました。実際に運用するときには、参照先のURIの内容のハッシュをトークンにもたせておくなどしてメタデータの正しさを担保する仕組みを用意する必要があるかもしれません。

Transferはトークンを他人に譲渡するボタンです。EtherscanはERC721には非対応ですが、ERC721のインターフェースは部分的にERC20と重なっているので、トランザクションの内容（誰から誰にトークンをどれだけ送ったか）はEtherscanでも確認できるようになっています。以下のURIは私がテスト用に作成したトランザクションです。

https://ropsten.etherscan.io/tx/0x5ebf764fa61c1dfa1953466bbed0a82c976c5cd74ba1213707fd56a131b90bbe

## まとめ

OpenZeppelinを利用して簡単なERC721トークンを作成し、フロントエンドを実装して遊べるようにしてみました。

次は、ERC721で物品管理アプリを作った話を書きます。

## 参考記事

* [Truffle で始める Ethereum 入門 \- ERC20 トークンを作ってみよう \- Qiita](https://qiita.com/amachino/items/8cf609f6345959ffc450)
* [OpenZeppelinのERC721が更新されたので見てみる \- Qiita](https://qiita.com/marutake/items/671719864e1fbbc757f1)
* [OpenZeppelinのERC721が更新されたので触ってみる \- Qiita](https://qiita.com/marutake/items/4236217e5203b5a8ceb7)