【コピペで出来る】独自コントラクトでNFTを出品する手順
この記事ではOpenSea等でNFTを出品する際、プラットフォーム上でアイテムを生成(ミント)せずに独自で作成したスマートコントラクトを使ってNFTを生成する手順を紹介します。
今回はできる限り少ないコーディングでの完成を目指します。
独自コントラクトとは
opensea.io からアイテムをミントした場合、トークンに紐づくスマートコントラクトは「OpenSea Collections」となります。
OpenSea の画面上で作成したアイテムの Contract Address を確認してみましょう。アイテムページの中に Contract Address が記載されているので、リンク先に飛んでみます。
すると、Etherscan(Polygonの場合はPolygonscan)のページに飛びます。Token Tracker の欄に 「OpenSea Collections」という表記がみえます。
これは、「このトークン(アイテム)はOpenSeaによって生成されたものである」ということを表しています。
一方で、筆者 cryptohaim.eth が独自コントラクトでミントしたアイテムの Contract Address を確認してみると、
Token Tracker が「CryptoHaim」(筆者自身)であることがわかります。
「OpenSea Collections」のようなプラットフォーム利用者が共通で使うスマートコントラクトに対して、各々が自分の名前を冠したスマートコントラクトを独自コントラクトと呼びます。
プラットフォーム上でミントしない理由
統治者がいなくても存続し続けるNFTにおいて、売買するプラットフォームとNFTの価値は本来無関係であるべきです。しかしながらNFTに紐づくスマートコントラクトに OpenSea の記載が残り続けるというのはいかがなものでしょう。自らの作品が「OpenSeaによって生成されたトークンである」とブロックチェーンに永久に刻まれることになります。自らのトークンを独自のスマートコントラクトで生成(ミント)し、プラットフォームに左右されることなくその価値を永続化させたくないでしょうか。
それでは独自コントラクトでNFTを作成(ミント)する手順を説明していきます。
NFT用のスマートコントラクト(ERC-721)の概要
まずはシステムの概要。
ミントされたNFTは、ブロックチェーンにその存在が記録されます。
しかしながらブロックチェーンは大容量データを記録することに弱いため、ブロックチェーン上にはNFTの持つ画像や動画といった作品の実態は記録されません(CryptoPunksのような特殊なケースを除く)。記録されるのは主に所有者と TokenURI のみ。
TokenURI はNFTのメタデータを表す以下のような JSON を返します。
{
"name": "The first",
"image": "https://xxxxx.com/my.jpeg",
"animation_url": "https://xxxxx.com/my.mp4",
"external_url": "http://www.xxxxx.com/my",
"description": "My first item."
}
image に作品の画像URLを設定すると、OpenSea等のプラットフォームで画像を表示できます。動画作品の場合は animation_url も設定します。
TokenURI の JSON やその image の永続性を考慮して IPFS化 する方法もありますが、IPFSについては別の記事で説明します。
開発手順
この記事では hardhat という開発ツールを使用します。他にTruffleという選択肢もあります。
node.js 12以上がインストールされていることが前提です。
ウォレットアプリである MetaMask もインストールしておいてください。
1. hardhat をインストールする
npm install --save-dev hardhat
2. OpenZeppelin をインストールする
OpenZeppelinというスマートコントラクトのライブラリをインストールします。
npm install @openzeppelin/contracts web3
3. hardhat プロジェクトを生成する
Create a basic sample project を選択してください。
npx hardhat
...
? What do you want to do? …
❯ Create a basic sample project
Create an advanced sample project
Create an advanced sample project that uses TypeScript
Create an empty hardhat.config.js
Quit
以下のようにプロジェクトのディレクトリ構成 が一通り作成されます。
root
┣ contracts
┣ scripts
┣ test
┣ hardhat.config.js
┣ node_modules
┣ package-lock.json
┣ package.json
┗ README.md
contracts 配下にスマートコントラクトの実態(ブロックチェーンにデプロイ出来るクラスのようなもの)を作っていきます。
scripts 配下には contracts を実行/参照 するjsスクリプト を作ります。
test 配下にはテストコードを作ります。スマートコントラクト開発においてテストは非常に大事なのですが、別の記事で細かく紹介したいのでこの記事では割愛します。
4. コントラクトを作成する
以下のように contracts/nft.sol を作成しましょう。
OpenZeppelinのライブラリ(ERC721PresetMinterPauserAutoId)を継承すればこれだけでミント機能を有したコントラクトが作れます。
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";
contract NFT is ERC721PresetMinterPauserAutoId {
constructor()
ERC721PresetMinterPauserAutoId("コントラクト名 *1", "シンボル *2", "https://xxxxx.com/ *3")
{}
}
*1 任意のコントラクト名を設定してください(筆者であれば CryptoHaim )
*2 任意のシンボル(コントラクトの省略名)を設定してください
*3 Base URI を設定してください。トークンはIDが0から順に割り振られていきます。Base URI に トークンの ID を繋げたhttps://xxxxx.com/0, https://xxxxx.com/1, https://xxxxx.com/2 ... がToken URIとなります。
IPFS化しない場合は、AWSのs3やGoogle Drive をBase URI にして JSONファイル を配信すればよいでしょう。
5. デプロイスクリプトを作成する
以下のように scripts/deploy.js を作成しましょう
const hardhat = require("hardhat");
async function main() {
const nftContract = await hardhat.ethers.getContractFactory("NFT");
const nft = await nftContract.deploy();
await nft.deployed();
console.log("NFT deployed to:", nft.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
6. ローカル環境を立ち上げる
まずはローカル環境で試してみましょう。以下コマンドでローカル環境にブロックチェーンネットワークが作成されます。
npx hardhat node
また、10000ETH 持ったアカウントが 20 個作成されます。(ローカル環境上のETHなので、ご自身のマシンの中でしか使えません。急に億り人になったわけではないので悪しからず)
作成されたアカウントのアドレス(公開鍵)と秘密鍵が表示されているかと思います。後ほど使うので1つの組み合わせをコピーしておいてください。
7. ローカル環境にデプロイする
開発環境を立ち上げたままにしたいので、新規タブを開いてください。
以下のコマンドを実行すると、ローカル環境にコントラクトがデプロイされます。
npx hardhat run --network localhost scripts/deploy.js
NFT deployed to: 0xxxxxxxxxxxxxxxx のようなログが吐かれるかと思います。0xxxxxxxxxxxxxxxx はコントラクトアドレスです。後ほど使うのでコピーしておいてください。
8. ミントスクリプトの作成
以下のように scripts/mint.js を作成しましょう。
const Web3 = require("web3");
const CONTRACT_ADDRESS = "コントラクトアドレス *1";
const PUBLIC_KEY = "アカウントのアドレス(公開鍵) *2";
const PRIVATE_KEY = "アカウントの秘密鍵 *3";
const PROVIDER_URL = "http://localhost:8545";
async function mintNFT() {
const web3 = new Web3(PROVIDER_URL);
const contract = require("../artifacts/contracts/nft.sol/nft.json");
const nftContract = new web3.eth.Contract(contract.abi, CONTRACT_ADDRESS);
const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest");
const tx = {
from: PUBLIC_KEY,
to: CONTRACT_ADDRESS,
nonce: nonce,
gas: 500000,
data: nftContract.methods.mint(PUBLIC_KEY).encodeABI(),
};
const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY);
signPromise
.then((signedTx) => {
const tx = signedTx.rawTransaction;
if (tx !== undefined) {
web3.eth.sendSignedTransaction(tx, function (err, hash) {
if (!err) {
console.log("The hash of your transaction is: ", hash);
} else {
console.log(
"Something went wrong when submitting your transaction:",
err
);
}
});
}
})
.catch((err) => {
console.log("Promise failed:", err);
});
}
mintNFT();
*1 コントラクトをデプロイしたときにコピーしたコントラクトアドレスに置き換えてください
*2 ローカル環境を立ち上げたときにコピーしたアカウントのアドレス(公開鍵)に置き換えてください
*3 ローカル環境を立ち上げたときにコピーしたアカウントの秘密鍵に置き換えてください
9. ローカル環境でミント
以下コマンドでローカル環境にNFTが1つミントされます(ID: 0)。
複数回実行するとIDが 1, 2, 3 と増えていきます。
npx hardhat run --network localhost scripts/mint.js
10. テストネットワーク環境のETHを手に入れる
デプロイやミントにはガス代が発生します。
本番ネットワークへ デプロイ / ミント すると実際のETHを使うため、必ずリハーサルとしてテストネットワーク環境で事前に試しましょう。
Ethereumのテストネットワーク環境として、Ropsten, Rinkeby, Goerli 等があります。Polygonのテストネットワーク環境には Mumbai があります。
テストネットワーク環境は、パブリックチェーンでありながら本番のETHとは違うテストネットワーク用のETHを使うため、実質無料でデプロイやミントを試すことができます。
OpenSeaのテストネット環境に対応している Rinkeby または Mumbai を使いましょう。
テストネットワーク用のETHを手に入れる手順説明は以下の記事に譲ります。
https://zenn.dev/kihonkei/articles/7750e99468a0ea
11. テスト/本番ネットワークに繋がるためのAPIを手に入れる
テストネットワークや本番ネットワークに繋がるためには、ノード(ブロックチェーンに参加しているマシン)からリクエスト(トランザクション)を投げる必要があります。自分のマシンをノードにするのは大変なので、APIを利用します。
テスト/本番ネットワークに繋がるためのAPI を提供しているサービスとして、 Alchemy や infura があります。
どちらも sign up してすぐにAPIのURLが手に入ります。
Alchemy で Rinkby を選択した場合、以下のような URL になります。
https://eth-rinkeby.alchemyapi.io/v2/xxxxxxxxxxxxxxxxxxx
12. テストネットワーク環境、本番ネットワーク環境へデプロイ
公開されたネットワークへデプロイしていきます。
hardhat.config.js を以下のように変更しましょう。
require("@nomiclabs/hardhat-waffle");
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.4",
networks: {
rinkeby: {
url: "Alchemy や infura で取得した rinkeby のURL",
accounts: ["自分のウォレットの秘密鍵"],
},
mainnet: {
url: "Alchemy や infura で取得した mainnet のURL",
accounts: ["自分のウォレットの秘密鍵"],
},
},
};
秘密鍵は絶対に漏らしてはならないので hardhat.config.js をうっかり公開しないように気をつけてください。(GithubのPublicリポジトリにpushしたりしないようお気をつけください)
Rinkeby 環境にデプロイする場合は以下のコマンドを実行してください。
npx hardhat run --network rinkeby scripts/deploy.js
成功していればRinkeby用の EtherScanから確認できます。
https://rinkeby.etherscan.io/
デプロイした時のログに吐かれているコントラクトアドレスを検索してみましょう。
13. テストネットワーク環境、本番ネットワーク環境でミント
続いてミントです。
scripts/mint.js の CONTRACT_ADDRESS 、PUBLIC_KEY、PRIVATE_KEY、PROVIDER_URL を環境に合わせて変更してください。
PROVIDER_URL は Alchemy や infura で取得した URL です。
そして実行。
npx hardhat run --network rinkeby scripts/mint.js
EtherScanで確認してみましょう。
14. OpenSeaで表示する
OpenSeaで表示してみましょう。
OpenSeaにもテストネット用の環境が用意されています。
https://testnets.opensea.io/
My Collections を開きましょう。
https://testnets.opensea.io/collections
「Import an existing smart contract」を開きます
次に「Live on a testnet」を開きます。
コントラクトアドレスを入力して Submit しましょう。
これで所有アイテムにミントした作品が表示されます!
表示されない場合は、
・トークンが発行されているか EtherScan を確認
・Token URI の先にJSONファイルを適切に配置しているか確認
してみましょう。
本番環境で実行するには、上記手順11-14を rinkeby から mainnet に読み替えて実施してください。
15. 最後に
今回は自力で独自コントラクトを実装する簡易的な方法を説明しました。
今後はガス代の節約方法、テストの書き方、IPFS化 を紹介して行く予定です。
私自身まだNFTに触れたばかりなため、誤った表現等あればご指摘いただけると幸いです。特に NFT や ブロックチェーン関連のエンジニアの方は是非交流しましょう!
不明な点、ご感想などあればコメントよろしくお願いします!