#はじめに
イーサリアムブロックチェーンにおける、スマートコントラクトの初めての規格として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を承認します
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を転送できるかどうかを返す
return(spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender))
spenderがオーナーか、トークンがspenderによって承認されてるか、isApprovedForAll
でspenderがtokenIdを転送できるかどうかを返します
####・safeTransferFrom(引数にdataを持たない)
function safeTransferFrom(address from, address to, uint256 tokenId) public {
safeTransferFrom(from, to, tokenId, "");
}
第四引数のdataを""としてsafeTransferFrom()
呼び出しています。
####・safeTransferFrom(引数にdataを持つ)
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
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
の実装は以下のようになっています。
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
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
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とは、「鋳る」という意味であり、トークンやコインを作成します。
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)を持たない
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
とは反対にトークンを削除する関数です
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が活用されると思いました。