初心者がWeb3.0っぽいプロダクトを作る時に、技術的にハマったところ・困ったところ
先日PairNFTというNFTを利用したマッチングサービスをリリースしました。
Web3.0分野のサービスをまずはひとつ作ってみたいと思っていた時に、NFTを使ったマッチング程度であれば難易度も高くなさそうだと考え、まずはラフに作ってみました。
このエントリでは、Web3.0のサービスを初めて作った筆者が、サービス構築において技術的にハマった点や開発全般において困った点などを記します。同じような状況の人の参考になれば幸いです。
前提
・ 筆者はインフラ〜フロントまで一通り触ったことがありますが、エンジニアリングは雰囲気です
・ ブロックチェーンやスマートコントラクトについては、数冊読んだ程度の知識量です
前提知識がとても多くて大変だった
ブロックチェーン周りの勉強を始めたのはリリースの2ヶ月前くらいで、それまではスマートコントラクトという言葉すら知らないレベルでした。DAppをどうやって作るかのイメージも特に湧いていませんでしたし、ブロックチェーンが何かということもよくわかっていませんでした。
用語を調べたり関連サービスを触ったり本を読んだりしていくうちに、なんとなく何を作ればDAppっぽいものが出来るのかはわかってきましたが、ここまでなんだかんだ1ヶ月くらいかかったと思います。過去の経験と照らし合わせると、初めてRailsを触った時の勉強量の3-4倍くらいだと思いました。
また、スマートコントラクトで利用する言語であるSolidityに関する知識だけでなく、周辺ツールの調査等々も必要であって、これがなかなかに時間をとりました。というのも、現状ではベストプラクティスと呼べるものはほぼ固まっておらず、日々新しいツールやサービスが出てきているため、その部分を調べることがなかなかに大変でした。日本語情報がほぼないということや、検索ワードがわからないということもあって余計に手間がかかった印象です。
やったこと
詳解 ビットコインはブロックチェーンがどのようなニーズから生み出されたのか理解することに役立ち、マスタリング・イーサリアムはスマートコントラクトやそれに関連する言葉を理解することに役立ちました。
手を動かすという観点では、CryptoZombiesがかなり良かったです。とりあえずコードを書いて雰囲気を掴みたいという方にはオススメです。
NFTがどうやって作られるのか全然知らなかった
NFTという名前は知っていても、NFTがどうやって作られていて、それがなぜOpenSeaなどのマーケットプレイス上で取引できるのか、などがよくわかっていませんでした。
本を読んだりしてもこの辺りの動きがよく分からなかったので(NFTの解説本などは抽象的な話が多い)、コードを読むことにしたのですが、これはかなり正解でした。
Openzeppelinが代表的なコードを公開しており、これがお手本のように書かれているコードなため、大変参考になりました。
なお、スマートコントラクトの特徴として、デプロイされたコードを誰でも読むことができるというものがあります。例えばこんな感じです。この辺りはこれまでのプロダクションのコードの概念と大きく違って面白いですね。
やったこと
OpenzeppelinのERC721(NFTの規格)のコードなどを読みました。変数にオーナーのアドレスとIDを突っ込むだけでNFTができるのか、、、というシンプルな学びが得られ、NFTという謎なものが身近なものに感じられるようになりました。
ローカルの環境構築がちょっと大変だった
スマートコントラクトは大体イーサリアム互換で動くので、とりあえずローカルでイーサリアムのブロックチェーンが動く環境があると捗ります。環境的にはGanacheを使うとボタン一つでブロックチェーンの環境が作れるので簡単でした。
Ganacheの提供元はTruffleというスマートコントラクトのビルドツールを提供しており、Truffleを使うとローカル環境へのスマートコントラクトのデプロイがわりと簡単に出来ます。
ところで、ローカル環境へのデプロイを行うためにもガス代が必要になるのですが、私はここでひとハマりしました。というのも、ウォレットやチェーンについての理解が浅すぎたこともあり、ローカル環境でのETHの獲得に手間取ってしまったのでした。この辺りは、下記の記事などで丁寧に説明されていたので参考にしてください。
やったこと
Truffleのチュートリアルを一通り行うことで、なるほど、スマートコントラクトはLambdaのようなものなのだな、、、というイメージがやっと鮮明になりました。
Truffleと似たツールにHardhatがあるのですが、現在はHardhatの方が人気なようです。私も最終的にはHardhatを利用するようになりましたが、この辺りはただの好みだと思います。
ウォレットでログインする仕組みを作るのが大変だった
Web3.0っぽいアプリといえば、ウォレットを利用したログインです。とりあえず最初は簡単そうだったという理由からWeb3.jsを利用したMetaMaskでのログインを実装しました。
MetaMaskでのログインを実装する流れなどはこの記事がわかりやすいです。
ウォレットを利用したログイン処理は、
- サーバー側で一度限り利用可能なランダムな文字列を発行
- クライアント側でその文字列を秘密鍵で署名
- 署名+自身のアドレス(公開鍵)をサーバー側に送信
- サーバー側で署名の検証
といった流れで行うのですが、これはとても良いなと思いました。パスワードをデータベース等に保存する必要はありませんし、メールアドレスの検証等々も必要ありません。これからもっと一般的になっていくのではないでしょうか。
MetaMaskを利用したログインのアプローチは途中でやめた
MetaMaskでのログインを実装し終わって色々試していると、モバイルだとうまく動かないという事象に気付きました。モバイルでも動くことは必須と思っていたので別の道を考えることにしました。
Web3.jsを利用したMetaMaskでのログインがPCでしか動かないのは、Web3.jsの仕様からくる問題です。超ざっくりいえば、MetaMaskなどのウォレットのブラウザ拡張機能をインストールすることで、ブラウザ内に window.web3
オブジェクトが勝手に作られるようになるのですが、モバイルだとこのような拡張を入れることができないため、当然のように window.web3
オブジェクトが存在しなくて無の挙動をするようになります。
そこで、やむなく別のアプローチを取ることにしました。
WalletConnectを利用する形にした
最終的にはWalletConnectを利用する形にしました。WalletConnectはモバイルにもデフォルトで対応していたり、MetaMask以外にも色々なウォレットに対応しています。そのため、わりと使い勝手が良いなと感じて選定しました。
しかし、WalletConnectをPCで使う場合はかなり難易度が高い操作になってしまいます。
- ウォレットに接続する処理を呼び出すと、モーダルが開く
- (MetaMaskの場合はPCに対応していないため)モーダルに表示されたQRコードをモバイルで読む
- QRコードを読むとモバイルのMetaMaskが起動する
- モバイルのMetaMaskで署名するとウォレットに接続される
といった流れを踏んだのち、
- 署名する処理を呼び出す(ウォレットに接続する処理とは別)
- モバイルのMetaMaskに通知がくるので署名する
という流れを踏まなければならず、正直初見だと意味不明なログインの流れになります。モバイルの場合は勝手にMetaMask等のウォレットのアプリが開かれるためそこまで悪い体験はないのですが、PCは慣れるまでかなり辛いなと感じました。ですが、現状でNFTを購入している層の多くはリテラシが高い人が多いはずなので、まあいいかなと思ってこの流れをやむなく実装しました。
やったこと
WalletConnectのチュートリアルを一通りやると、WalletConnectを利用したログインの大体の流れは掴めました。処理自体はweb3.jsを使うよりも簡潔に書ける印象はありました。
チェーン選定がよくわからなかった
DAppについて調べていると「チェーン選定が何よりも大事」といった記述が目立っていたのですが、そもそもチェーンごとの違いなどは何も分かっておらず、選定といっても何を基準に選べばいいかもよくわかっていませんでした。(というか、今もあまりよくわかっていません)
とてもざっくりいえば
- 現状でそのチェーンはどれくらいの人に使われているか
- 今後そのチェーンの利用者は伸びていくのか
- ガス代はどの程度か。それは運営者やユーザーにとって許容範囲か
- 関連するツール等の対応状況はどうか
あたりがポイントになりそうですが、どれも結構抽象的です。利用者数でいえばイーサリアム一択なのですが、ガス代がとても高いことから避けました。また、元々はOpenSeaを利用してNFTを販売しようとしていたので、OpenSeaが対応しているPolygonを最初は選択していました。
その後ゆるゆる情報収集していると、tofuNFTならAstarも利用できるということだったので、せっかくですし日本発のパブリックブロックチェーンであるAstarに乗っかることにしました。ここにはノリ以外の理由は特にありません。
やったこと
色々なチェーンのdiscordに入ってみたりして、発言の量なんかをみていました。なんとなく盛り上がってる感が大事なんだろうな、、、以外のことは正直わかりませんでした。
あとは各チェーンにdeployしてみたりして、どれくらいの費用や時間がかかるのか、ということを見ていたのですが、正直ここは時間帯やタイミングによる差も大きく、よくわからないな、、、となりました。(PolygonやAastarであれば、deployは1円以下なのはほぼ確定していたので、正直なんでも良いやとなりました)
どうやってスマートコントラクトを呼び出すかわからなかった
スマートコントラクトはLambdaのようなものではあるのですが、Lamdaのようにどこかの企業が運営しているサーバーを利用するわけではなく、どこかに散らばる誰かのコンピューター上でその処理が実行されます。
この「誰か」を特定する必要はないのですが、誰かに繋がるための入り口となるノードを見つけなければいけません。ノードのIPアドレスを知っていれば良いのですが、そのノードが常に動いているとは限らないため何かしらの手段でこの問題を解決する必要があります。この問題に対してのツールとしては、イーサリアムであれば Infura がデファクトになっているようで、Infura が発行したURLを接続先に指定することで安定的にスマートコントラクトを呼び出せるようになります。
Astarにおいては OnFinality が使われることが多いようです。Astarのヘルプ記事 にも書かれていますね。
コードのイメージとしてはこのような感じです。
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("infura や OnFinality から取得したURL"))
# abi はJSONで書かれたスマートコントラクトとのインターフェース仕様のようなもので、
# solidity で書かれたファイルをビルドすると自動で生成されます。
contract = w3.eth.contract(address="呼び出したいスマートコントラクトのアドレス", abi=abi)
count = contract.functions.count().call()
やったこと
テスト環境(Ropstenなど)にスマートコントラクトをデプロイして、Infura経由で叩いたりして遊んでいました。ただの文字列が返ってくるだけのスマートコントラクトだとしても、動いたらなんか嬉しかったです。
お気持ち
ブロックチェーンは分散型の思想で作られているはずなのですが、Infura などを使わないと現実的にはスマートコントラクトを呼び出すことは難しく、分散型のはずなのにどこかの誰かに依存せざるを得ない状況にモヤっとしてしまいました。現在は過渡期であるためやむを得ないとは思いますが、そのうちこの問題も自然と解決するような気はします。
NFTのメタデータをどこに持たせるべきかを悩んだ
NFT=デジタル画像と認識している方も多いと思いますが、NFT自体はただのブロックチェーン上に存在する一意のIDでしかありません。そのため、画像本体やNFTの名前など(メタデータ)は通常別の場所に保存されています。たとえば、NFTのIDに対応する形のAPIのエンドポイントを作り、そこからメタデータを取得するといった運用がとられたりします。
とはいえここには大きく3つの流派があります。
- サービス提供者が運営しているAPIからメタデータを返す
- S3やGCSなどのファイルサーバーにメタデータを保存し、それを返す
- 後述のIPFSにメタデータを保存し、それを返す
最初はOpenSeaのNFTのチュートリアルにあるようにAPIでメタデータを返す形で実装していたのですが、なにやらDecentralized警察に引っかかるようで、最終的にはメタデータもIPFSに保存する形にしました。
それぞれメリットデメリットがあるのですが、画像が消えてしまう残念な気持ちを考えるならば、IPFSに保存してく方が良いのかなとは思います。
デプロイするための暗号通貨を手に入れるのが大変だった
実のところ、一番大変だったのはここかもしれません。
スマートコントラクトをデプロイするためにはガス代(暗号通貨)が必要になるのですが、この暗号通貨の購入がハンター試験のクエストのような感じでなかなか大変でした。
暗号通貨を手に入れる方法は大きく三つあって
- 誰かからもらう
- 取引所で買う
- 他の暗号通貨と交換する
このどれかを選択することになるのですが、今回選択したAstarは日本の取引所で買うことはできないため、海外の取引所の口座を作る、もしくは、日本の取引所で購入した他の暗号通貨を使って交換する、というアプローチが必要になります。
暗号通貨の交換においても、手数料の高い場所や低い場所があったりと、どの交換所を利用すれば良いかを調べたりするのがなかなかに手間で、Aコイン→Cコイン→Dコイン→目的のコイン、、、みたいなルートを辿ることもしばしばです。
結局、今回はJPYCを入手した後にAstarに交換するという方法を取ることにしました。ちょうど良いタイミングでJPYCがAstarに対応したこともあって、比較的簡単にAstarを手に入れることができました。
AstarチェーンのJPYCを手に入れた後は ArthSwap で JPYC → WASTR → ASTRへと変換すると Astar が簡単に手に入ります。(が、少し交換のレートが高いようなので、もっと安価に手に入れたい人は頑張って調べてみてください)
補足
JPYCにはPolygonのJPYCやAstarのJPYCがあるのですが、これは全く別物だったりします。同じJPYCという名前なのですが、Polygonだけで利用可能なJPYC、Astarだけで利用可能なJPYCという意味合いになります。ですので、Astarを手に入れたい場合はAstarのJPYCを手に入れる必要があります。
執筆時点では、異なるチェーン間のJPYCの交換は公式には提供されていないようです。とはいえ、別の暗号通貨に交換することでチェーン間の実質的なJPYCの移動も可能になります。しかしガス代が余分にかかるだけなのであんまり良いアプローチではないかなと思います。
その他(Web3.0関係ない部分も含む)
pip install web3 がうまく動かなかった
ローカル環境が汚かったからなのか、 pip install web3
がしたいだけなのにうまく動かないという事象で数時間を溶かしました。
結論としては、素直にDocker環境を作るとすんなり動きました。
NFTの画像はIPFSを使うようにした
NFTの画像の本体はどこかのクラウドサーバーにあることが多いのですが、それだと少しWeb3.0っぽくないので、IPFSにアップするようにしました。
IPFSはざっくりといえば、分散型のファイルサーバーのようなもので、ファイルの key を指定すれば近くの誰かのサーバーからいい感じにファイルを持ってきてくれるような仕組みです。ファイルサーバーの提供者には、対価としてトークンが配られたりします。このあたりはWeb3.0っぽいですね。
IPFSの利用自体にあまり難しいことはなく、Infuraを使うとAPIを叩くだけでIPFSへのファイル追加が行えます。AWS S3などにアップする時よりも、気持ち遅いかな、、、みたいな感覚ですが、許容範囲内です。
.env は .gcloudignore に入れよう
Google App Engine に deploy した際に、なぜだかうまく動かないことが多くてぐぬぬしていました。
.env
がそのままデプロイされてしまったことが原因だったようで、 .gcloudignore
に .env
を記載してあげると問題は回避できました。
最初から多言語化するとあんまり大変じゃない
NFT関連のサービスの多くは英語で提供されていることもあって、今回は最初から多言語化対応する形にしました。英語と日本語の設定ファイルを書くのは多少面倒なのですが、途中から多言語化するより断然楽に作ることができました。
まとめ
一日数時間くらい作業して、だいたい3週間くらいでリリースまでもっていきました。この作業時間には、設計やデザイン、規約作成等々も含むので、実際にコードを書いていた時間はもうちょっと短いと思います。その中でもブロックチェーン関連のコードを書いたのは10時間くらいかと思います。(それもほとんどが調査でした)
実態としてはDAppというよりは一部の処理でスマートコントラクトを呼び出すだけの普通のWebアプリケーションであって、作業時間の8割くらいは TypeScript/React を書いているか Python/Flask を書いている時間でした。Firestoreに慣れてなさすぎたせいでハマった時間も長く、本来であればもうちょっと早くリリースできたかな、という気はします。
とはいえ今回の目的はブロックチェーン周辺技術を利用した開発の流れを理解することだったので、自分としてはわりと満足です。次はもう少しスマートコントラクトをゴリゴリ書くようなものを作ってみようかなと思いました。おしまい。
おまけ: 使ったアーキテクチャ
- API
- Google App Engine
- Python
- Flask
- Web3.py
- Cloud Firestore
- Front
- Firebase Hosting
- TypeScript
- React
- Tailwind
- WalletConnect
- SmartContract
- Astar/Shiden/Shibuya
- Solidity
- Hardhat
- OnFinality
- IPFS
- Infura
Discussion