NFTを購入する際にエンジニア目線で確認したい3つのポイント~ケーススタディ~
みなさん、こんにちは。Fintertechの高橋です。
私はプライベートでAstar Networkのアンバサダーとして活動させてもらっていたり、DAOに興味を持っているので、和組のDiscordに参加させてもらったりして活動をしています。ブロックチェーンに技術的な関心があるので、Substrateのコードを自分で読んだり、独自Palletを追加したり、Solidityやrustでスマートコントラクトを実装してテストネットでテストしたりしています。そんな私ですが、実はNFTを購入するのは今回が初めてでした。
そこで今回は、ブロックチェーンの検証性を意識して確認しながら、NFTを購入する方法をご紹介したいと思います。
NFTについて
そもそもNFTとは何かについては弊社の大島が以前の記事で言及していますので、そちらをご参照下さい。
私は今回技術的な面でもう少し、補足をさせて頂こうと思います。Ethereum上ではNFTはERC-721という標準規格で規定されたプログラミング仕様のことを指します。Solidityの標準実装で有名なOpenZeppelinでもERC-721実装が存在します。ソースコードをみていただくとわかるのですが、ERC721は大雑把に言ってしまうと「名前」「シンボル」「ID」「トークンの保管先URL」をEthereum上のアドレスに紐付けたものです。技術的に言うと実はそれ以上でもそれ以下でもありません。実はNFTとはブロックチェーン上でIDのついたものを登録するだけのものなのです。これは事実なのですが、そう言ってしまうと外側からNFTに入ってきた人は非常に驚かれるかもしれません。
ERC-721の応用
先にも述べたID管理をするだけのERC-721を応用して、様々な物の所有権をブロックチェーンに実装しようとしているのが、最近の所謂「NFT」というものになります。NFTのデータ管理方法には色々な形態があり、管理したいデータをERC-721の実装を拡張してチェーンに直接保存する方法や、データ本体をIPFSといった分散ストレージに保管する方法、はたまた企業の管理するサーバーで管理する方法と様々なものがあります。管理したいデータとは、「デジタル絵画」「アニメ画像」「様々な動画」「所有権を示すもの」等、多岐にわたっています。
管理方法の違いとは?
NFTの付帯データの管理方法については、個人の立場によって是非論が議論されている状態のものでもあります。私はブロックチェーンに技術として非常に注目しているので、ブロックチェーンそのものに保管する方式やIPFSなどの分散型の仕組みに保管する方式を推したい気持ちですが、これは各個人が現時点で何を「信頼」するかによって大きく変わるものです。現時点では未完成の部分が多い技術よりも、世間で名前が通った企業を信頼したい人もいるでしょう。そういった人は企業が管理する基盤にのっているNFTのほうが安心出来るのかもしれません。一旦ここでは、その是非論はおいておきたいと思いますが、データの管理方法の違いは、サービス提供者やサービス利用者が何を信頼するかの違いだと思います。
初めて購入したいと思った「山古志村NFT」
繰り返しになりますが私は日ごろからブロックチェーンに技術として注目していますが、仮想通貨の値段の上がり下がりや有名アーティストのNFTがいくらで売れたという所謂価格変動には一切興味がありません。最終的にどういう役割を果たす形で収まるかは未だ未知数ですが、DAOでの動きを見ている限り「トークンという存在は、そのコミュニティの価値を媒介出来る形にしたものである」と私は思っています。犬の名前を模した仮想通貨が物凄い値段をつけたりするのは、ただのバブルの中のノイズの一環くらいにしか思っていません。そんな理由もあって、自分のお金を出して購入するトークンには「今までになかったコミュニティがその存在や価値を世に示すもの」であって欲しいと思い続けてきました。
前置きが長くなりましたが、そこで出会ったのが今回のNFTです。限界集落に向かいつつある村が世界とのつながりを作り関係人口を増やし、新しいコミュニティとして再生したいと願うこの取り組みこそ、私が思っていたトークンの「カタチ」の原型なのではないかと直感的に思ったのが購入の経緯です。山古志のNFTについてはこちらの公式記事をご参照ください。
NFT購入のステップガイド
まず、以下の購入サイトに行きました。
「PURCHASE NOW」ボタンを押すと下にスクロールして、購入画面に行きます。
画面を見ると錦鯉をあしらったデジタルアートのNFTの購入代金として「0.03ETH」(購入時点で約1万円)であることが記載されています。「PURCHASE」ボタンがあるので、押すと「CONNECT WALLET」ボタンが出力されますので、押します。(※これを押した段階では購入はされません。)
私はMetamaskを使用しているので、そちらのボタンを押すと「購入行為として0.03ETHを支払うトランザクションにデジタル署名するか否か」を問う画面がMetamaskに出力されます。何も考えないとこのまま「承認ボタン」を押すのですが、ブロックチェーン技術で実装されているということは検証性が高い事を意味しますので、今回はその検証をきっちり行ってから購入することにします。
購入コントラクトの中身をチェックする
まず、Metamaskが立ち上がった時点の画面をもう一度見ると、Metamaskの画面上部に自分のアドレス→「どこかのアドレス」が記載されています。この「どこかのアドレス」がコントラクトアドレスになりますのでコピーしておきます。次にEthereumの動作を検証出来るサイトEtherScanに移動します。コピーしておいたアドレスを貼ると以下ような画面に遷移します。
まず、一通りのコードを確認していきます。メイン実装である「Nishikigoi.sol」を見ていきます。
・インポート文とコントラクトの定義を見ると、sodlityの0.8系で実装されていること、openzeppelinのERC-721実装であることがわかります。
pragma solidity 0.8.6;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
<中略>
contract Nishikigoi is iNishikigoi, ERC721, ReentrancyGuard, Ownable {
using Strings for uint256;
uint256 public constant PRICE = 0.03 ether;
uint256 public constant MAX_SUPPLY = 10000;
uint256 private _nextReservedTokenId = 9800;
uint256 private _remainingAmountForPromotion = 200;
uint256 private _remainingAmountForSale = 9800;
uint256[] private _mintedTokenIdList;
address payable private _recipient;
bool private _isOnSale;
bool private _isMetadataFroze;
string private __baseURI;
constructor(
string memory baseURI,
address payable __recipient
)
ERC721("Nishikigoi", "KOI")
{
require(__recipient != address(0), "Nishikigoi: Invalid address");
_recipient = __recipient;
__baseURI = baseURI;
}
・購入関数を見て、問題ない実装になっていることを確認します。
function buy(uint256 tokenId) external override nonReentrant payable {
require(_isOnSale, "Nishikigoi: Not on sale");
require(msg.value == PRICE, "Nishikigoi: Invalid value");
require(tokenId < _nextReservedTokenId, "Nishikigoi: Invalid id");
_mintedTokenIdList.push(tokenId);
_remainingAmountForSale--;
_safeMint(_msgSender(), tokenId);
}
・一番最後まで見て、不審な実装がないことを確認します。
・例えば、ERC-721実装のTransferは以下のように実装されています。
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
require(_exists(tokenId), "ERC721: operator query for nonexistent token");
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
}
・大切なのは「require」の部分になります。満たす条件として「_isApprovedOrOwner」関数がありますが、これが存在しないと他者が誰でも自由に自分のトークンを転送出来てしまいます。
・そういった意味でもこのコントラクトは「メイン実装に不審な点がない点」「OpenZeppelinのERC-721実装を使っており、他者に自由に転送されてしまう恐れがない点」等からも安全なコントラクトである可能性が高いと言えます。
注意:この解析は筆者の現時点の知識レベルに依存します。本記事はコントラクトのセキュリティを保証するものではありません。
トークンの保管先を確認する
冒頭でお話しした通り、自分が何を信頼するかによってデータの預け入れ先の価値が変わります。私は、企業よりも技術を信頼したいので、そういう預け入れ先になっているかを確認します。
ERC-721には「tokenURI」という関数があり、それで確認出来ます。今回のNFTの場合、メイン実装である「Nishikigoi.sol」に実装がありますが、実装を見ると「baseURI」に「tokenID」を付加したものになっていることがわかります。「baseURI」は定数等になっていないので、実装からは格納先が分かりません。
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "Nishikigoi: URI query for nonexistent token");
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) : "";
}
そこで先行購入者の方の情報を見せてもらって確認します。
EtherScanの「Read Contract」の「tokenURI」関数をコールして確認してみます。tokenURIは関数は「view」定義になっており、ガス代を必要としません。見てみると格納先が「ipfs://QmXzrJpTM9Rz9xK6WzPinLZDNvNvtuL1MrREceE6ttTWMZ/1.json」になっています。「ipfs」となっていて、私としては高得点です。
ここでは省略しますが、IPFSは分散ストレージのことです。こちらの記事が参考になると思いますので、詳細を知りたい方はご確認下さい。
IPFSのデータを確認する
IPFSのデータは色々な方法で確認できますが、お手軽にブラウザで確認出来るので、今回はBraveブラウザを使用して確認していきます。
Braveブラウザを立ち上げて、URL欄に先ほどのIPFSのURLを打ってみます。すると・・・。
格納されているjsonの中身が確認できます。さらにこの中には"image"というタグが確認出来、ここにデジタルアートが格納されていることを推測させます。そこもipfsに格納されているようなので、確認してみましょう。
確認出来ましたね。
これで購入前の確認は完了です。私個人の判断として、正しく実装されたスマートコントラクトと、個人が信用出来る格納先にデータが保管されることが確認出来ましたので、購入を進めたいと思います。
購入する
先ほどのMetamaskに戻って「承認」を押します。
トランザクションが実行されます。
トランザクションの実行が完了しますと、ETHERSCANとOPENSEAのURLリンクが出て、自分のNFTの存在が確認出来るようになります。これで購入は完了です。
まとめ
今回はNFTの購入について、ブロックチェーンの透明性、検証性の高さに注目した検証の仕方を説明させて頂きつつ、NFT購入体験を紹介させて頂きました。
繰り返しになりますが、個人的な意見としてトークンはコミュニティの力を表す媒介物だと思っており、現時点では世の中にその価値を表現出来ていない価値あるコミュニティがその価値を世に問うツールとして存在しているのだと思っています。私は、ブロックチェーンやトークン、そこから生まれたDAOなどを通して、世界の価値あるコミュニティが持続可能性を持つ事が出来ると思っています。
そして最終的に、地球と共存する持続可能性の高いコミュニティがブロックチェーンなどの技術を通じて、世界中に構築されることを夢見ています。
そういう観点から、今回の山古志村のNFTの活動には大きな可能性を感じています。
余談
コントラクトを読んでいて、思ったことがあります。購入資金を引き出す関数が実装されているのですが、これが単なる、いちアドレスへの送金になっています。これが問題かと言えば全く問題はありません。
function withdrawETH() external override onlyOwner {
Address.sendValue(_recipient, address(this).balance);
}
ふと思った程度ですので、間違っているかもしませんし、不遜な物言いに聞こえてしまったら申し訳ございませんが、この引き出し先として村の中での活動や事業を一定の単位にDAOとして分割して、DAOをブロックチェーン上に実装し、そのDAO宛にのみ送金される実装だとしたなら、より一層感動したと思った次第です。そんなことが出来るコミュニティがあれば、即座に移住したくなるなと、そんなことを思いました。
それにしても今回の山古志村の活動は、こういう活動に注目していた人間を大いに感動させ、引き付けて余りあると思っています。
※当記事はNFT購入の推奨を目的とするものではございません