個人開発でNFTに対応した画像投稿サービス「MUNI」をリリースした話(CloudRun + Next.js + Solidity)
個人開発でNFTに対応した画像投稿サービス 「MUNI」をリリースしました。MUNIは課金することで投稿した画像をNFT化することができるサービスです(投稿自体は無料)。最近は個人開発が盛んで多くの開発者の体験談などが巷に溢れかえっていますが、 NFTやブロックチェーンを利用した開発体験談を今まで見かけたことがなかった ので誰かの役に立てればと開発体験談を書いてみました。
- シンプルな画像投稿サービス
- 画像の投稿は無料
- 投稿した画像のNFT化は有料(3000円)
- NFT化すればOpenSea等の外部オークションサイトで取引可能
システムの全体像と構成する要素技術
MUNIを支えている技術はこんな感じです。面倒なところは外部のマネージドサービスを利用することで開発コストを下げています。また可能な限り固定費のかからないサービスを利用して日々のランニングコストを下げています(大事)。 少し特殊な点として、実行時間が読めないNFTの発行する処理はPubSubを使ってCloudFunctionsに切り出している点でしょうか。(後述)
バックエンド | CloudRun + Express + PubSub + CloudFunctions |
フロントエンド | CloudRun + Next.js + Tailwind.js |
スマートコントラクト | Solidity + truffle |
決済処理 | Stripe |
ユーザー認証 | Firebase Authentication |
DB | Firebase Firestore |
ビルドツール | CloudBuild |
NPMレポジトリ | Github Package |
実際に開発してみた雑感としては
良かった点
- CloudRunはCloudBuildとの相性が良くて簡単にCD環境を構築できて便利。
- フロント周りは少ない手数でいい感じのUI・レスポンスタイムを実現できるNext.jsとTailwindで構築したが開発体験がかなり良い。標準的な機能はすべて提供されているだけでなく日本語での記事等も多くハードルも低い。
悪かった点
- SQLのマネージドサービスは固定費がかかるものが多く、個人開発で低価格で使えるサービスがあまりない。致し方なくFirestoreを使ったが学習コストもそれなりに掛かって不便
- CloudRunがCloud Load Balancerの利用を前提にしているところがある。検証環境等を構築するために外部接続を制限したりするためには、自分で頑張って専用のリバースプロキシを建てるか、Cloud Load Balancer+IAP等で実現する形になってコスト増
CloudRun + CloudBuild + Next.js + Tailwindあたり組み合わせて最初に開発環境ををしっかりと整えておくと後段の開発が非常にスムーズになるので開発環境周りは初期にしっかり構築しておくのが大事です。
NFTの実装とか
MUNIの他の個人開発と違う点はNFT周囲の話です。MUNIではSolidity+truffleを使ってイーサリアムメインネットにNFTをデプロイしました。
いま流行りの単語であるNFTですがその中身はEVM(イーサリアムバーチャルマシン)上で動作する非常にシンプルなスマートコントラクトです。スマートコントラクトとはブロックチェーン上で動作するイミュータブル(改変不可能)なプログラムのようなもので、EVMの場合にはSolidityという言語で記述されることが多いです。ここら辺の話はマスタリング・イーサリアムに詳しい記述があるのでそちらに譲ります。
イーサリアムで動作するNFTの標準的な仕様はERC721で定義されていて、実態としてはトークン番号から現在の所有者とメタデータのURLを返す辞書型(ハッシュマップ)のようなものです。OpenZeppelinというブロックチェーン企業がこれらのよくテストされた標準実装が提供してくれているので、NFTを自ら開発する場合にはそれらを利用するのがいいでしょう。MUNIにおいてもOpenZeppelinのERC721を継承して作成されています。 スマートコントラクトは一度ネットワーク上にデプロイされたら最後二度と編集することができないので、自力で一から開発するのではなくよくテストされたライブラリ等を用いて全体のコード量を減らすことでバグの混入を徹底的に防ぐのが大事です。
Solidity + truffleでのスマートコントラクト開発
イーサリアムスマートコントラクトはSolidityと呼ばれる言語で記述&コンパイルしてEVMバイトコードとABIを生成することが一般的です。開発の流れは以下の通りです。
- Solidityでコントラクトを記述
- コンパイルしてEVMバイトコード(ネットワークにデプロイするバイナリ)とABI(Application Binary Interface:コントラクトを呼び出す際に用いるインターフェース)の定義ファイルを生成
- truffleを使ってテストネットワークにコントラクトをデプロイ
- truffleを使ってコントラクトをテスト
- 十分にテストしたらメインネットワークにデプロイ
truffleはSolidityの開発を支援するためのフレームワークで、作成したスマートコントラクトのテストやネットワークへのデプロイを支援してくれます。truffleを使えばjavascriptを経由してスマートコントラクトを呼び出して動作を検証することができます。truffleが無しでもweb3js等でスクラッチで書くこともできますが、余計な開発コストがかかるだけなのでフレームワークは積極的に利用するべきです。MUNIでは2-5あたりはコマンド一つで実行できるように自動化しているので、短いサイクルで開発と検証を繰り返すことができました。
またERC721も1から作っている訳ではなくOpenZeppelinで提供されるライブラリを元に大部分が作成されていて個人で開発した部分はほとんどありません。特にContract Wizardを使えば必要な項目をポチポチするだけでコード生成してくれるので専門的な知識が無くてもERC721準拠のコントラクトを作るだけなら非常に簡単にできます。
バックエンドからのスマートコントラクトの呼び出し
スマートコントラクトの実行にどれくらい時間がかかるかはEVMの状況次第で場合によっては数分かかるので、HTTPサーバー内ではNFT化のリクエストを受けたらタスクキューに突っ込むだけ突っ込んで、クライアントには秒でレスポンスを返したい気持ちがありました。
そこでリクエストを受けた際にはGCPのPubSubにメッセージを突っ込んで、実際のNFT発行処理はメッセージを受け取ったCloudFunctionsが実行しています。注意点としてCloudFunctionは540秒の実行時間制限があるのでおすすめできません。 具体的な代案がない状況ですがNFTの発行は場合によっては長時間に渡る可能性があるのでタイムアウトを十分長く取れるようなサーバーを自分で立てるか、EVMから発行されるイベントを監視するなどの方法を取るほうが好ましいです。
CI・CD環境
個人開発はとにかく開発リソースが足りないのでテストであったりデプロイなどの作業は早い段階で可能な限り自動化して時間を捻出するのが大事だと考えています。 MUNIではフロント・バックエンドともにDockerを利用してコンテナ化しています。
GCPを使っているのであればGitの特定ブランチを監視してCloudBuild起動→CloudRunにデプロイが比較的簡単に実現できるので小規模なサービスであれば、CloudBuildとCloudRunの組み合わせはおすすめです。
一方でCloudBuildは1つの設定ファイルに対して1つのコンテナが原則(できなくはない)なので、複数のサーバーを立てて内部的に通信したりする場合にはcloudbuild.yamlが複雑化する傾向がある気がします。実際にMUNIもサーバー間のバージョンの整合性が取れなくなったりみたいな問題が発生しているのでここは未だ課題として残っています。
他感想とか
- モチベーション維持するの難しいので開発期間は可能な限り圧縮して、早めにリリースするのを心がけるのが大事
- リリース後のマーケティング戦略は考えて置いたほうがいい。Twitter広告などはポリシーで広告できる内容が結構厳しく制限されているので事前に確認しておいたほうがいい。
- 折角web3技術採用したんだから認証機能もweb3的にやればよかった。
- 折角なので今後ビルド環境はterraformやbazel使ってアップデートしていきたい
Discussion
NFT化に3000円とのことですが、直接OpenSeaから出品するのとどんな違いがあるのでしょうか?
本音を言うとOpenSeaに劣後してしまうのですが、こちらのサービスの特徴としてコントラクトを工夫して作者情報をオンチェーンに保存できるようにしています。画像投稿サイトなので第三者がイラストを自分のものと偽ってULしても、作成者のウォレットかどうかを見ればそれが偽物かどうか検証できるようになっています。純粋なERC721コントラクトは現在の所有者のウォレットしか保持していないので、このような画像投稿サイトでの真正性の証明は難しいと考えています。
「安心して自分の渾身の作品を展示できて、それの真正性を担保できる美術館サイト」というのが当初のコンセプトでした。
実はOpenSeaにあまり詳しくないのですがOpenSeaは最初のトランザクションが発生するまでNFTに変換されないのでオークション等にかけることが前提のサービス設計だと考えています(間違っていたらごめんなさい)。こちらのサービスは決済が終わった直後にコントラクトを実行するので、決済後すぐにオンチェーンになりますので取引目的ではなく自分の作品をNFTにして展示したい方向けのサービスという感じです。