NFTを自分で作ってみた~Remix上でERC721、ERC1155トークンを実装

最近ブロックチェーン技術に興味があり色々調べています。前回はERC20規格のトークンを作成しました。

ai-china.hatenablog.com

今回はNFTとして普及しているERC721規格のトークンをRemix上で実装します。自力でNFTを作るのは面倒だったので、デジタルデータを販売したい場合はOpenSea等の既存のマーケットプレイスを使った方が便利です。

作成するERC721トークンの概要

今回作成するトークンの概要は下記の通り

  • 名称:Shanghai Token
  • シンボル:SHT
  • Base URI:Google Apps ScriptでJSONを返す処理をデプロイしたURL
  • ERC721(Ethereum Request for Comments: Token Standard #721)規格

ERC721規格のNFTはブロックチェーン上にデジタルデータを保管している訳ではありません。デジタルデータの保管場所等に関する情報を返却するURIを保持しているだけです。

事前準備(NFT化するデジタルデータを用意)

トークンを作成する前に、まずNFT化する「デジタルデータの配置」と「JSON形式のデジタルデータ情報」を用意する必要があります。また仮想通貨ウォレットMetaMaskも事前に準備します。(MetaMaskは前回記事で書いたので詳細は割愛)

1.デジタルデータの配置

今回はディープラーニングで作成した女性の顔画像をNFT化します。

f:id:denim012:20211230111956j:plain

▼DCGANで画像を作った過去記事

ai-china.hatenablog.com

テスト用のNFTなので、現在使用しているブログサービス(はてなブログ)に保存されているjpeg画像をそのまま利用します。

2.JSON形式デジタルデータ情報

トークン作成時に指定するBase URIはデジタルデータ情報をJSON形式で返却する必要があります。

Google Apps Scriptで下記コードを作成し、デプロイしました。

function doGet(e) {
  return ContentService.createTextOutput(JSON.stringify({
    "name": "DCGAN Idols",
    "description": "This is most rare item in the world.",
    "image": "https://cdn-ak.f.st-hatena.com/images/fotolife/d/xxxxxxxxxxxxxxxxxx.jpg"
})).setMimeType(ContentService.MimeType.JSON);
}

デジタルデータに関する「名称(name)」「説明(description)」「画像URL(image)」を返却します。画像URLは1.で配置した場所を指定します。

f:id:denim012:20211230112109p:plain

※注意

1.2.の情報は共にブロックチェーンの管理対象外(オフチェーン)です。NFT作成後でもこれらの情報は簡単に書き換えることが出来てしまいます。改ざんを防ぐためIPFS(InterPlanetary File System)を利用する事が多いようですが、今回は使用していません。

RemixでERC721のトークンを実装

Webブラウザ上で動作する統合開発環境Remixを使用して、ERC721のトークンを実装します。

Remix - Ethereum IDE

今回もOpenZeppelinで提供している「ERC721」に関するライブラリを使用します。公式サイトの「Contracts Wizard」を使用したためコードは自動生成されたものです。

OpenZeppelin

1.コードの記述

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ShanghaiToken is ERC721, Ownable {
    constructor() ERC721("Shanghai Token", "SHT") {}

    function _baseURI() internal pure override returns (string memory) {
        return "https://script.google.com/macros/s/AKfycbz6jWCIlufkVQuH5WOhGv9SMcVMo5e_xxxxxxxxx/dev/";
    }

    function safeMint(address to, uint256 tokenId) public onlyOwner {
        _safeMint(to, tokenId);
    }
}

これだけでトークンの作成は完了です。

2.コードをコンパイル

▼Remix上でCompileボタンを押下

f:id:denim012:20211230112732p:plain

3.Ropsten環境にデプロイ

▼EvvironmentにInjected Web3を選択しDeploy

f:id:denim012:20211230112743p:plain

コンストラクタにはMint処理が無いので、この時点ではトークン(NFT)保有者はいません。

4.NFTの作成・発行

このNFTを作成・発行するために「Mint」処理を行います。

▼SafeMint関数を呼び出します。引数は「誰に与えるか」、「トークンIDを何番にするか」

f:id:denim012:20211230112854p:plain

5.NFT保有者を変更

MetaMask上ではERC721トークンの送信には対応していないので、safeTransferFrom関数を実行します。

▼引数に「誰から渡すか」「誰に渡すか」「トークンID何番を渡すか」入力し実行します。

f:id:denim012:20211230112246p:plain

所有者が移りました。MintやTransfar処理にはそれぞれ手数料ETHが必要です。

etherscanのサイトではより詳細な情報を確認可能です。

f:id:denim012:20211230112228p:plain

6.「MetaMask」で作成したNFTを表示

仮想通貨ウォレットMetaMaskはERC721規格のトークンにも対応しているため、NFTを確認可能です。

▼1つ保有していることが確認出来ました。

f:id:denim012:20211230112202p:plain

ただ、PC Chrome版MetaMaskではNFTに紐づいている画像を表示することは出来ません。

7.NFTに紐づく画像を表示

マーケットプレイスOpneSeaのテスト環境で自作NFTが正しく表示されるか確認します。(https://testnets.opensea.io/  は「Rinkebyネットワーク」を使用)

今まで作成したコードを「Rinkebyネットワーク」にデプロイ・MINTします。

Opneseaで自作トークンを表示

下記アドレスで自分のトークンをOpneSeaで表示します。

https://testnets.opensea.io/assets/(コントラクトアドレス)/(TokenId)

f:id:denim012:20211231105726p:plain

画像が正しく表示されていません。デジタルデータ情報を返却するJsonが正しくないようです。

base URIで指定したJsonの検証

下記OpenSeaの検証用サイトで問題内容を確認します。

https://testnets-api.opensea.io/asset/(コントラクトアドレス)/(TokenId)/validate

f:id:denim012:20211231110534p:plain

「InvalidTokenUrlResponseException: Invalid response, please change your 'Content-Type' header from 'text/html' to 'application/json'」のエラーが発生しています。

ただ、URLを直接開くとGoogle Apps Scriptで作成したJsonが正しく返ってきています。

そこで通信の詳細を確認しました。GASでデプロイしたURLにアクセスすると一度リダイレクトしてからJsonを返しています。

▼通信1

f:id:denim012:20211231111345p:plain

▼通信2

f:id:denim012:20211231111354p:plain

OpenZeppelinで提供している「ERC721」ライブラリではこのようにリダイレクト後に返却する値は取得できないようです。

base URIの修正

仕方が無いので、レンタルサーバー上にjsonファイルを配置して再度デプロイ・MINTすることでOpenSea上で画像の表示が出来ました。

NFTマーケットプレイス(OpenSea)でNFT作成 

ここまではコードを記述しNFTを作成するまでをまとめました。自分でデジタルデータの配置場所などを用意する必要があり少し面倒です。

今度は世界最大のNFTマーケットプレイス「OpenSea」の出品機能を使って画像をNFT化します。

▼登録した内容(テストネット使用)

DCGAN Idols - OpenSea

2021年12月現在OpenSeaでデジタルデータを登録するとERC721規格ではなく、ERC1155規格のトークンが作成されます。ここ最近規格が変わったようで3か月前に出品されたデータはERC721規格となっています。

OpenSeaでは登録自体は無料(ERC1155トークンのMINTにかかるガス代はプラットフォーム側が負担している?)でした。売却申し込み時にガス代がかかるのと、売買成立時に2.5%の手数料をOpenSeaに支払う必要があります。

参考:ERC1155トークンの実装

OpenSeaで現在使われているNFTの規格ERC1155も自作してみました。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts@4.4.1/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts@4.4.1/access/Ownable.sol";

contract ShanghaiToken1155 is ERC1155, Ownable {
    constructor()
        ERC1155("https://www.xxxxxxx.com/test.json/")
    {}

    function setURI(string memory newuri) public onlyOwner {
        _setURI(newuri);
    }

    function mint(address account, uint256 id, uint256 amount, bytes memory data)
        public
        onlyOwner
    {
        _mint(account, id, amount, data);
    }

    function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
        public
        onlyOwner
    {
        _mintBatch(to, ids, amounts, data);
    }
}

ERC721と比べMINT関数の引数が増えています。

f:id:denim012:20211231163752p:plain

MINT後OpenSeaでトークンの内容を確認しました。

f:id:denim012:20211231163933p:plain

トークンの作成や画像保存場所まで正しく認識できています。

まとめ

NFTで使用されているERC721規格とERC1155規格のトークンを作成しました。

自分でコードを書いてNFTを作成するには対象のデジタルデータをサーバーに保管し、情報をJSON形式で返却する仕組みが必要です。単純に画像などを販売したいだけならOpenSeaなどのマーケットプレイスを利用した方が簡単で便利です。