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


この記事は Ethereum Advent Calendar 2018 の14日目の記事です。

はじめに

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

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

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

概要

今回TokenURIの元になるデータは構造体に格納することにしました。
そしてTokenURIでサーバーを叩き、その構造体にアクセスする設計にしています。
このように設計するとブロックチェーン以外のDBを使用せずにERC721を実装することが出来ます。
TokenURIの設定方法についてはこちら

コントラクト

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 2017年をみて、プログラミング未経験ながら色々参考にさせてもらっていて、今回書くことが出来て感慨深かったです。

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