はじめに
読者のみなさまGM、NFT開発Gの西澤です。
普段Coincheck NFTの開発を行っています。
実はわたくし、エンジニアになる前はパティシエだったんです!
ある日会社にイタリアンプリンを作っていったところ、同僚に一言いただきました。
「せっかくNFT開発GなんだからNFTでもプリン作ったらどう?」
ここからNFT作成が始まりました。
構想を練る
NFTマーケットプレイスに携わっておきながら自分でNFTを作ったことないというのはいかがなものでしょう。
クリエイターの目線に立って考えられるよう、そして、理解を深められるようぜひとも作ってみようではないかと意気込みました。
さて、どんなNFTを作ろう?
考えたところ3つの特徴を持つNFTを作成することに決めました。
- プリン
- とりあえず、これです。どれをとってもプリンなNFTにしよう。
- フルオンチェーン
- IPFSなど外部にメタデータや画像を置くNFTが多いですが、どうせなら純粋にすべてのデータがブロックチェーン上にあるNFTを作ります。
- ジェネレーティブ
- スマートコントラクト内で自動的に画像が組み上がってこそ実装の面白みがありそうなので、mint時に5つレイヤーのパーツがランダムに組み合わさって出来上がる仕様にしました。
パーツを描く
イラストなんか描いたことがないのでなるべくシンプルにします。
まずは落書き
これをイラストレーターでパーツごとに描いていきます。
出来上がったパーツたちをSVGとして出力していきます。
contract実装
基本的な環境、実装方法は以下のサイトを参考にさせていただきました。
HOW TO WRITE & DEPLOY AN NFT
https://ethereum.org/ja/developers/tutorials/how-to-write-and-deploy-an-nft/
How To Create On-chain NFTs with Solidity
https://medium.com/coinmonks/how-to-create-on-chain-nfts-with-solidity-1e20ff9dd87e
それぞれ作成方法が詳細に記載されており、サンプルがすんなりとできたので、実装は簡単だろうとたかを括っていました。
.
.
.
そんな訳ありませんでした。
パーツのSVGデータが重すぎて定数として定義できない
まずこれです。
当初、SVGをBase64にした画像パーツの文字列をcontract上に定数として定義しようとしていました。
パーツ数は44個、合計179キロバイトです。
これらを定義して、contractをデプロイしようとしたところgas limitを超えてしまいできませんでした。
Blockのgas limitを超えてしまったようです。
※2022/11/06現在のBlock gas limitは3000万
しょうがないので各パーツを格納する空の配列を定義しておき、contractのデプロイ後、別トランザクションとしてパーツデータを設定するようにしました。
string[] private PARTS_BOTTOM
string[] private PARTS_FACE;
string[] private PARTS_PUDDING;
string[] private PARTS_TOP;
function setBottom(string memory bottom) public onlyOwner {
PARTS_BOTTOM.push(bottom);
}
function setFace(string memory bottom) public onlyOwner {
PARTS_FACE.push(bottom);
}
function setPudding(string memory bottom) public onlyOwner {
PARTS_PUDDING.push(bottom);
}
function setTop(string memory bottom) public onlyOwner {
PARTS_TOP.push(bottom);
}
パーツのランダムな組み合わせどうするか問題
mintの度にランダムでパーツが組み合わさるように実装を行っていました。
唯一無二というのがNFTなので、同じパーツの組み合わせを作成したくありません。
単純にmintした組み合わせを記憶しておき、次のmint時には記憶された組み合わせの場合はもう一度ランダムで組み合わせを計算する実装を行おうと思っていました。
この場合、mintすればmintするほど再計算の量が多くなってしまい、最後の1個の組み合わせのmintをする時に最もgasがかかってしまいます。
そこで、二次元配列形式で組み合わせデータを用意しようと思いました。
[
[ボトムパーツindex, 顔パーツindex, 本体パーツindex, トップパーツindex],
...
]
組み合わせ数は11760通りあり、すべてを定義すると膨大なgasがかかってしまうので、もう少し工夫する必要に迫られました。
いろいろと考えた結果、uint256型の一次配列で定義することにしました。
1つのアイテムのパーツの組み合わせを16進数4桁で表現します。
ボトムパーツindex=1, 顔パーツindex=2, 本体パーツindex=3, トップパーツindex=4
の場合は
0x1234
という形になります。
この塊を16組つなぎ合わせます。
ボトムパーツindex=1, 顔パーツindex=2, 本体パーツindex=3, トップパーツindex=4
ボトムパーツindex=9, 顔パーツindex=8, 本体パーツindex=7, トップパーツindex=6
ボトムパーツindex=3, 顔パーツindex=5, 本体パーツindex=2, トップパーツindex=8
...
の組み合わせを表現する場合、
0x123498763528...
という形になります。
これでuint256型で16種類の組み合わせを表現することができました。
これを735個定義することで11760通りの表現ができます。
それが、こちらです。
使用方法は、ランダムに一次配列からuint256数値を取り出し、その数値の末尾4桁を取り出しmintに使用します。
使用後は4桁分元の数値をシフトし、0になればその数値は配列から削除します。
これでmintごとのgas量は一定となり、組み合わせの重複もなくなります。
完成!
その他、こまごまと壁にぶつかりながらもなんとか完成しました!
OpenSea: https://opensea.io/ja/collection/generative-pudding
contract: https://etherscan.io/address/0x34549995595c9e8dda7cf8149a27c7390420b7bd#code
所感
さらっと書いていますが、着手から完成まで5ヶ月くらいかかりました。
慣れない絵を描く時間が一番かかってますw
その他、gas priceが5Gweiの時にデプロイを始めたのに手順にまごついている間に急に10Gwei代に上がり泣きそうになったり、
途中までデプロイしたのにも関わらずパーツの設定を誤り重複して設定してしまい、contractを再作成するはめになり大泣きしそうになったりしています。
それもこれも含めてmainnetに上げてこのようにお披露目できる状態になったことは喜ばしいことです。
手を動かして実際にやってみることで得られるものはとても大きいです。
今後もちょこちょこ作っていこうと思います。
最後に
NFT開発グループではエンジニアを募集しています!
カジュアル面談もできますのでお気軽に応募してください!
その他多くの部署でも募集していますので少しでも気になった方は興味ある部署のページを覗いていただくだけでも嬉しいです。