Ethereum入門　〜ERC721 TokenURIとサーバー編〜

この記事は [Ethereum Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ethereum) の14日目の記事です。

#はじめに

先月このようなdappsを作りました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">dApps作りました。<br>画像をIPFSに登録して、スマートコントラクトで所有権の売買が出来るdAppsです。ありがちなユースケースですが、今回は勉強も兼ねてマーケットプレイスも自作しました。<br><br>Rinkeby対応です。無料なのでdApps触った事ない人にも使って貰えると嬉しいです！<a href="https://t.co/TaHDBH12wY">https://t.co/TaHDBH12wY</a> <a href="https://t.co/WpUxlMFvwq">pic.twitter.com/WpUxlMFvwq</a></p>&mdash; Kohei Tanaka (@kolife01) <a href="https://twitter.com/kolife01/status/1062274910628659200?ref_src=twsrc%5Etfw">2018年11月13日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

技術スタックは簡単に書くとこのような感じです。

<img width="733" alt="Screen Shot 2018-12-13 at 10.23.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/200669/f793689d-ba5a-01c2-9e95-24acc4e64373.png">

この実装をする上でERC721　TokenURIの設定とサーバーの役割が勉強になったので今回はそのことについて書きたいと思います。

#概要
今回TokenURIの元になるデータは構造体に格納することにしました。
そしてTokenURIでサーバーを叩き、その構造体にアクセスする設計にしています。
このように設計するとブロックチェーン以外のDBを使用せずにERC721を実装することが出来ます。
TokenURIの設定方法については[こちら](https://qiita.com/kolife/items/8bc7316d25a24db08ceb)

##コントラクト

ERC721に関するコントラクトはこのような感じです。

```
pragma solidity ^0.4.24;

import "github.com/OpenZeppelin/openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "github.com/OpenZeppelin/openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol";

contract dappic is ERC721Full, Ownable {

    constructor () ERC721Full("dappic" ,"DPP") public {
    }

    struct picture {
        string ipfsHash;
        address publisher;
        string name;
        string description;
    }
    
    mapping (uint=>picture) public pictures;
    mapping (string => bool) ipfsHashUploaded;

    string public tokenURIPrefix = "https://dappic.glitch.me/picture?id=";

    function tokenURI(uint _curseId) public view returns (string) {
        bytes32 tokenIdBytes;
        if (_curseId == 0) {
            tokenIdBytes = "0";
        } else {
            uint value = _curseId;
            while (value > 0) {
                tokenIdBytes = bytes32(uint256(tokenIdBytes) / (2 ** 8));
                tokenIdBytes |= bytes32(((value % 10) + 48) * 2 ** (8 * 31));
                value /= 10;
            }
        }

        bytes memory prefixBytes = bytes(tokenURIPrefix);
        bytes memory tokenURIBytes = new bytes(prefixBytes.length + tokenIdBytes.length);

        uint i;
        uint index = 0;
        
        for (i = 0; i < prefixBytes.length; i++) {
            tokenURIBytes[index] = prefixBytes[i];
            index++;
        }
        
        for (i = 0; i < tokenIdBytes.length; i++) {
            tokenURIBytes[index] = tokenIdBytes[i];
            index++;
        }
        
        return string(tokenURIBytes);
    }

    function updateTokenURIPrefix(string _tokenURIPrefix) public onlyOwner {
        tokenURIPrefix = _tokenURIPrefix;
    }

    function mintpicture(string _ipfsHash, string _name, string _description) public {
        require(!ipfsHashUploaded[_ipfsHash]);
        ipfsHashUploaded[_ipfsHash] = true;

        picture memory _picture = picture({ipfsHash: _ipfsHash, publisher: msg.sender, name: _name, description: _description});
        uint256 newpictureId = totalSupply() + 1;
        pictures[newpictureId] = _picture;
        _mint(msg.sender, newpictureId);
    }

}
```

###コード解説

まずTokenURIの元になるデータは構造体で管理

```
struct picture {
        string ipfsHash;
        address publisher;
        string name;
        string description;
    }
```

TokenURIを叩くと、あるサーバー叩きにいく処理にします。
solidityは文字列の連結がそのままでは処理出来ないため、string型をbytes型に変換して処理しています。

```
string public tokenURIPrefix = "https://dappic.glitch.me/picture?id=";

    function tokenURI(uint _curseId) public view returns (string) {
        bytes32 tokenIdBytes;
        if (_curseId == 0) {
            tokenIdBytes = "0";
        } else {
            uint value = _curseId;
            while (value > 0) {
                tokenIdBytes = bytes32(uint256(tokenIdBytes) / (2 ** 8));
                tokenIdBytes |= bytes32(((value % 10) + 48) * 2 ** (8 * 31));
                value /= 10;
            }
        }

        bytes memory prefixBytes = bytes(tokenURIPrefix);
        bytes memory tokenURIBytes = new bytes(prefixBytes.length + tokenIdBytes.length);

        uint i;
        uint index = 0;

        for (i = 0; i < prefixBytes.length; i++) {
            tokenURIBytes[index] = prefixBytes[i];
            index++;
        }

        for (i = 0; i < tokenIdBytes.length; i++) {
            tokenURIBytes[index] = tokenIdBytes[i];
            index++;
        }

        return string(tokenURIBytes);
    }
```



##サーバー

あとはサーバーでidのリクエストに応じて、web3経由で構造体の中身を取得しレスポンスを返してあげればOKです。

```index.js
var express = require('express');
var app = express();
var url = require('url');

var Web3 = require('web3');
var web3 = new Web3();


web3 = new Web3(new Web3.providers.HttpProvider("https://rinkeby.infura.io/"));

var abi =[
//省略
...
]

var address = "0xXXX" //contract address;

var contract = web3.eth.contract(abi);
var instance = contract.at(address);

app.get('/picture', async function(request, response) {
  
    var urlParts = url.parse(request.url, true);
    var parameters = urlParts.query;
    var pictureId = parameters.id;
  
	var picture = await instance.pictures(pictureId);
	var owner = await instance.ownerOf(pictureId);

    response.json(
      { 
        id: `${pictureId}`,
	publisher: `${picture[1]}`,
	name: `${picture[2]}`,
	image: `${picture[0]}`,
	description: `${picture[3]}`,
	owner: `${owner}`,
	
	});

});
```


#おわりに
雑になりましたがERC725のTokenURIとサーバーの使い方について書かせて頂きました。
丁度一年前に、このEthereum Advent Calendar 201７年をみて、プログラミング未経験ながら色々参考にさせてもらっていて、今回書くことが出来て感慨深かったです。

最後まで読んで頂きありがとうございました。