ERC721とは?CryptoKitties(暗号猫)でも使われているEthereumの規格

Ethereum(イーサリアム)

巷を騒がせているEthereumのDAppsゲームであるCryptoKitties(暗号猫)

これにはERC721というEthereumの決まりが適用されています。
今回はこのERC721について調べてみました。

Sponsored Link


概要

ERC721とは、Ethereum(イーサリアム)ブロックチェーンにおける、スマートコントラクトの規格の一つでERC20ERC223などの規格が存在します。

ERC721の特徴は、スマートコントラクト内で代替え不可能なトークン(Non-Fungible Token = NFT)を扱えるように規格を定められている点です。
ERC721の規格を用いることで、イーサリアムブロックチェーン上でNFTの所有権や取引履歴を記録できます。

ERC721が生まれた背景

Ethereum上で機能しているスマートコントラクト上で自分達の好きなようにトークンを作成することができますが、様々な取引所やウォレットでは新しいトークンが作成されるたびに取り扱いできるようにシステム改修などの対応をしなければならず、かなりフラストレーションを溜めていました。

そこで取り扱いが楽なように2015年11月19日にERC20, 2017年3月5日にERC223が生まれました。
また新しいタイプのトークンを作成するために、ERC20をベースに「代替え不可能なトークン」として拡張できるERC721の規格が生まれました。

代替不可能とは?

「代替不可能」を知るにはまず「代替可能」を考えます。

代替可能とは?

トークンを大量にあった場合に、同じ種類(たとえばETH)の通貨であれば、どのトークンも違いはなく同じトークンだといえます。
ようは、同じ通貨のトークンであればどれもいっしょだと、という意味です。1つとして、違うトークンはないと。
林檎はいくつあっても林檎ですよね。同じ品種であれば、産地がどこであっても、その価値や果たしている機能は同等。
(厳密に言えば林檎は大きさや、甘さなど違いますがここでは無視します)

これを「Fungible(代替可能)」といいます。

では代替不可能とは?

でも、紹介したようなDAppsのゲームで扱われている、土地や有名人のカード、自分の育てた猫のようなものを代替え不可能と考えます。
同じ土地は1つもないですし、人もそれぞれの人です。それぞれがユニークです。

1つとして同じものがないのです。

これをトークンに例えると、1つ1つのトークンが別々の個性を持つことになり、これがCryptoKitties(暗号猫)のように自分のオリジナルな猫をトークンにのせブロックチェーン上で管理できます。
この管理できる仕組みのスマートコントラクトの規格がERC721です。

ERC721の活用例

  • Crypto Kitties。自分の育てた猫を取引
  • ゲーム産業向け「Enjin Coin」。ゲームプラットフォーム間を横断してアイテムの売買
  • 出生管理(アメリカのイリノイ州は出生管理に分散型台帳を導入するテストを2017年8月31日にはじめている

ERC721の仕様詳細

上記のissueで議論が進められていますが、こちらまだ2018年2月16日現在、議論されているようで正式なリリースとはなっていないようです。
が、現在のドラフト版ですが定義されているメソッドをさっくり記載しておきます。

ERC20互換性があるメソッド

ERC20と互換勢があるメソッド一覧は以下です。基本的な情報を返す

  • name – 名前を返す
  • symbol – シンボルを返す ex.ETH
  • totalSupply – 合計供給量を返す
  • balanceOf – オーナーが保持している残高を返す

所有権を管理するメソッド

ownerOf

function ownerOf(uint256 _tokenId) constant returns (address owner)

_tokenId から 所有するオーナーのアドレスを返す

approve

function approve(address _to, uint256 _tokenId)

トークンの所有者を_toに移動させることを承認する。

takeOwnership

function takeOwnership(uint256 _tokenId)

送信者に指定したトークンの所有権を写す。ただし、事前に送信者にapproveされている場合しか成功しない

transfer

function transfer(address _to, uint256 _tokenId)

トークンの所有者を移転する。

tokenOfOwnerByIndex

function tokenOfOwnerByIndex(address _owner, uint256 _index) constant returns (uint tokenId)

オーナーが所持している_index番目のトークンIDを返却する

NFT Metadata

tokenMetadata

function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl)

tokenのメタデータ(name, image, description, other metadata)を示す
multiaddress(合成可能なURL)の文字列を返却する

Events

Transfer

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId)

所有者が移転した場合に発行されるイベント

Approval

event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId)

approve()メソッドが成功される場合に呼び出されるイベント

具体的なソース例

以下がsimpleな実装でよさそうでしたが、tokenMetadataなど一部メソッドが実装されていませんでした。

また、現時点では issue に実装例が記載がなかったため
(Reference implementation forthcoming…と書いてありました)

github searchで”ERC721″で検索したところ以下のソースがスターが一番ついていたのでこちらを参考にしました。ソース量ありますが関連があるソース全部のっけてます。

また、このソースは以下プロジェクトのソースです。

ERC721のインターフェース

ERC721.sol

https://github.com/dharmaprotocol/NonFungibleToken/blob/master/contracts/ERC721.sol

pragma solidity 0.4.18;

/**
 * Interface for required functionality in the ERC721 standard
 * for non-fungible tokens.
 *
 * Author: Nadav Hollander (nadav at dharma.io)
 */
contract ERC721 {
    // Function
    function totalSupply() public view returns (uint256 _totalSupply);
    function balanceOf(address _owner) public view returns (uint256 _balance);
    function ownerOf(uint _tokenId) public view returns (address _owner);
    function approve(address _to, uint _tokenId) public;
    function getApproved(uint _tokenId) public view returns (address _approved);
    function transferFrom(address _from, address _to, uint _tokenId) public;
    function transfer(address _to, uint _tokenId) public;
    function implementsERC721() public view returns (bool _implementsERC721);

    // Events
    event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
}

DetailedERC721.sol

https://github.com/dharmaprotocol/NonFungibleToken/blob/master/contracts/DetailedERC721.sol

pragma solidity 0.4.18;

import "./ERC721.sol";

/**
 * Interface for optional functionality in the ERC721 standard
 * for non-fungible tokens.
 *
 * Author: Nadav Hollander (nadav at dharma.io)
 */
contract DetailedERC721 is ERC721 {
    function name() public view returns (string _name);
    function symbol() public view returns (string _symbol);
    function tokenMetadata(uint _tokenId) public view returns (string _infoUrl);
    function tokenOfOwnerByIndex(address _owner, uint _index) public view returns (uint _tokenId);
}

NonFungibleToken.sol

https://github.com/dharmaprotocol/NonFungibleToken/blob/master/contracts/NonFungibleToken.sol

pragma solidity 0.4.18;

import "./DetailedERC721.sol";


/**
 * @title NonFungibleToken
 *
 * Generic implementation for both required and optional functionality in
 * the ERC721 standard for non-fungible tokens.
 *
 * Heavily inspired by Decentraland's generic implementation:
 * https://github.com/decentraland/land/blob/master/contracts/BasicNFT.sol
 *
 * Standard Author: dete
 * Implementation Author: Nadav Hollander nadav at dharma.io
 */
contract NonFungibleToken is DetailedERC721 {
    string public name; // トークン名
    string public symbol; // トークンシンボル

    uint public numTokensTotal; // トークン数合計

    mapping(uint => address) internal tokenIdToOwner; // トークンIDとオーナーアドレスマップ
    mapping(uint => address) internal tokenIdToApprovedAddress; // トークンIDと承認アドレスマップ
    mapping(uint => string) internal tokenIdToMetadata; // トークンIDとメタデータマップ
    mapping(address => uint[]) internal ownerToTokensOwned; // オーナーが所持しているトークン一覧マップ
    mapping(uint => uint) internal tokenIdToOwnerArrayIndex; // トークンIDとオーナー配列インデックスマップ

	// トークン移動した時のイベント
    event Transfer(
        address indexed _from,
        address indexed _to,
        uint256 _tokenId
    );

	// 承認時のイベント
    event Approval(
        address indexed _owner,
        address indexed _approved,
        uint256 _tokenId
    );

	// オーナー所有のトークンIDが指定されているかチェックする
    modifier onlyExtantToken(uint _tokenId) {
        require(ownerOf(_tokenId) != address(0));
        _;
    }

	// トークン名を返す
    function name()
        public
        view
        returns (string _name)
    {
        return name;
    }

	// シンボル名を返す
    function symbol()
        public
        view
        returns (string _symbol)
    {
        return symbol;
    }

	// トークンの合計供給量
    function totalSupply()
        public
        view
        returns (uint256 _totalSupply)
    {
        return numTokensTotal;
    }

	// アドレスのトークン残高
    function balanceOf(address _owner)
        public
        view
        returns (uint _balance)
    {
        return ownerToTokensOwned[_owner].length;
    }

	// 指定されたトークンを所有しているアドレス返却
    function ownerOf(uint _tokenId)
        public
        view
        returns (address _owner)
    {
        return _ownerOf(_tokenId);
    }

	// トークンIDのメタデータ取得
    function tokenMetadata(uint _tokenId)
        public
        view
        returns (string _infoUrl)
    {
        return tokenIdToMetadata[_tokenId];
    }

	// 指定されたアドレスにトークンIDの承認権限を付与
    function approve(address _to, uint _tokenId)
        public
        onlyExtantToken(_tokenId)
    {
        // 送信元がトークンIDの所有者
        require(msg.sender == ownerOf(_tokenId));
        // 送信元と送信先が異なる
        require(msg.sender != _to);

        // 承認用アドレスがマスターアドレスでない 
        // または送信先がマスターアドレスでない
        if (_getApproved(_tokenId) != address(0) ||
                _to != address(0)) {
            // 承認用アドレス登録
            _approve(_to, _tokenId);
            Approval(msg.sender, _to, _tokenId);
        }
    }

    // トークン所有者移転(承認者が、送信元から送信先にトークン移転)
    function transferFrom(address _from, address _to, uint _tokenId)
        public
        onlyExtantToken(_tokenId)
    {
        // プログラム実行者がトークン承認者
        require(getApproved(_tokenId) == msg.sender);
        // 送信元がトークンのオーナー
        require(ownerOf(_tokenId) == _from);
        // 送信先がマスターアドレスでない
        require(_to != address(0));

        // 承認権限削除 & 送金
        _clearApprovalAndTransfer(_from, _to, _tokenId);

        Approval(_from, 0, _tokenId);
        Transfer(_from, _to, _tokenId);
    }

    // トークン所有者移転(プログラム実行者が、送信先にトークン移転)
    function transfer(address _to, uint _tokenId)
        public
        onlyExtantToken(_tokenId)
    {
        // プログラム実行者がトークン所有者
        require(ownerOf(_tokenId) == msg.sender);
        // 送信先がマスターアドレスでない
        require(_to != address(0));

        // 承認権限削除 & 送金
        _clearApprovalAndTransfer(msg.sender, _to, _tokenId);

        Approval(msg.sender, 0, _tokenId);
        Transfer(msg.sender, _to, _tokenId);
    }

    function tokenOfOwnerByIndex(address _owner, uint _index)
        public
        view
        returns (uint _tokenId)
    {
        return _getOwnerTokenByIndex(_owner, _index);
    }

    function getOwnerTokens(address _owner)
        public
        view
        returns (uint[] _tokenIds)
    {
        return _getOwnerTokens(_owner);
    }

    function implementsERC721()
        public
        view
        returns (bool _implementsERC721)
    {
        return true;
    }

    function getApproved(uint _tokenId)
        public
        view
        returns (address _approved)
    {
        return _getApproved(_tokenId);
    }

    function _clearApprovalAndTransfer(address _from, address _to, uint _tokenId)
        internal
    {
        _clearTokenApproval(_tokenId);
        _removeTokenFromOwnersList(_from, _tokenId);
        _setTokenOwner(_tokenId, _to);
        _addTokenToOwnersList(_to, _tokenId);
    }

    function _ownerOf(uint _tokenId)
        internal
        view
        returns (address _owner)
    {
        return tokenIdToOwner[_tokenId];
    }

    function _approve(address _to, uint _tokenId)
        internal
    {
        tokenIdToApprovedAddress[_tokenId] = _to;
    }

    function _getApproved(uint _tokenId)
        internal
        view
        returns (address _approved)
    {
        return tokenIdToApprovedAddress[_tokenId];
    }

    function _getOwnerTokens(address _owner)
        internal
        view
        returns (uint[] _tokens)
    {
        return ownerToTokensOwned[_owner];
    }

    function _getOwnerTokenByIndex(address _owner, uint _index)
        internal
        view
        returns (uint _tokens)
    {
        return ownerToTokensOwned[_owner][_index];
    }

    function _clearTokenApproval(uint _tokenId)
        internal
    {
        tokenIdToApprovedAddress[_tokenId] = address(0);
    }

    function _setTokenOwner(uint _tokenId, address _owner)
        internal
    {
        tokenIdToOwner[_tokenId] = _owner;
    }

    function _addTokenToOwnersList(address _owner, uint _tokenId)
        internal
    {
        ownerToTokensOwned[_owner].push(_tokenId);
        tokenIdToOwnerArrayIndex[_tokenId] =
            ownerToTokensOwned[_owner].length - 1;
    }

    function _removeTokenFromOwnersList(address _owner, uint _tokenId)
        internal
    {
        uint length = ownerToTokensOwned[_owner].length;
        uint index = tokenIdToOwnerArrayIndex[_tokenId];
        uint swapToken = ownerToTokensOwned[_owner][length - 1];

        ownerToTokensOwned[_owner][index] = swapToken;
        tokenIdToOwnerArrayIndex[swapToken] = index;

        delete ownerToTokensOwned[_owner][length - 1];
        ownerToTokensOwned[_owner].length--;
    }

    function _insertTokenMetadata(uint _tokenId, string _metadata)
        internal
    {
        tokenIdToMetadata[_tokenId] = _metadata;
    }
}

MintableNonFungibleToken.sol

トークン作成用のコントラクト。mintメソッドを使ってトークンを生成する。

https://github.com/dharmaprotocol/NonFungibleToken/blob/master/contracts/MintableNonFungibleToken.sol

pragma solidity 0.4.18;

import "./NonFungibleToken.sol";
import "zeppelin-solidity/contracts/math/SafeMath.sol";


/**
 * @title MintableNonFungibleToken
 *
 * Superset of the ERC721 standard that allows for the minting
 * of non-fungible tokens.
 */
contract MintableNonFungibleToken is NonFungibleToken {
    using SafeMath for uint;

    event Mint(address indexed _to, uint256 indexed _tokenId);

    modifier onlyNonexistentToken(uint _tokenId) {
        require(tokenIdToOwner[_tokenId] == address(0));
        _;
    }

    function mint(address _owner, uint256 _tokenId)
        internal
        onlyNonexistentToken(_tokenId)
    {
        _setTokenOwner(_tokenId, _owner);
        _addTokenToOwnersList(_owner, _tokenId);

        numTokensTotal = numTokensTotal.add(1);

        Mint(_owner, _tokenId);
    }
}

まとめ

ERC721は今後様々な分野でデファクトスタンダードになりそうです。そしてゲームにもさらに使われるのではないかと。
誰の所有権でいつ移動したか?がパブリックになるのはいいですね。不正ができない。全部調べることができる。

今度ERC721の簡単なサービスだしてみよう、そうしよう。

Stainless steel fixing | Detail on Saltdean "pier" | Flickr

Sponsored Link

ビットコイン(Bitcoin)、イーサリアム(Ethereum)、リップル(Ripple)、ネム(NEM)などの仮想通貨を買うならZaifがおすすめです。

特に国内でETHを購入するなら、アルトコインでも板取引に対応しているZaif(ザイフ)が割安なのでよいですよ。

コメントはまだありません

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA


仮想通貨全般
Stablecoin(ステーブルコイン)とは?

最近Stablecoinのツイートをよく見ており、次の仮想通貨の本命などと言われています。 ステーブ …

仮想通貨全般
難易度調整(Difficulty Adjustment)アルゴリズムとは?

最近、Monaが攻撃を受けて被害がでた件ですが 上記、大石さんの記事を引用しますが 攻撃を容易にした …

ICO
ICO Platformはどんなものがあるか?

ICO Platform にどんなものがあるか調べてみました。 ここで取り上げているICO Plat …