こんにちは。金融ソリューション事業部の姫野です。
- 1. 今回実装する内容
- 2. 利用サービス
- 3. 実装詳細
- 4. 総括
1. 今回実装する内容
以前のブログで、web3ウォレットを手軽に提供するWaaS(Wallet as a Service)について比較検討しました。本ブログでは、その中からthirdwebが提供するweb3サービスを使用し、SNSログインでNFTを発行するシステムを構築しました。このシステムは誰もが簡単にNFTを取得できるように設計されており、非常にユーザーフレンドリーです。さらに、このブログの手順に従えば簡単に同様のシステムを構築できますので、ぜひ挑戦してみてください。
1.1 実装内容のキーポイント
1.2 ユーザー体験の流れ
- Webサイトにアクセス
- Googleログイン
- NFT取得ボタンをクリック
- NFTの受け取り
1.3 ガス代について
ブロックチェーンのトランザクションには手数料(ガス代)がかかります。ガス代は、トランザクションを実行するために必要な計算リソースのコストをカバーするもので、トランザクションの複雑さやネットワークの混雑状況に応じて変動します。通常、NFTを受け取る際にはNFT自体の価格が0であってもユーザーの手数料が発生しますが、本システムでは実装者側が代替することで解消可能です。具体的な手法については後述します。
2. 利用サービス
今回の実装には、thirdwebのサービスを主に使用しました。thirdwebは開発者にとって扱いやすく、APIとの連携もシンプルです。一つのサービスプロバイダーを利用することで、APIキーの管理が一元化され、開発プロセスがより効率的になります。このため、他のプラットフォームを組み合わせることなくthirdwebサービスに統一して構築しました。
基本的にthirdwebのサービスは無料で利用でき、今回は無料の範囲で構築しましたが、一部機能やアクセス数に応じて有料となる場合があるため、詳細はこちらを参照してください。
2.1 thirdwebサービス
2.1.1 Connect
thirdwebのConnectは、ウェブサイトに訪れたユーザーが自動的にweb3ウォレットを生成できる機能です。この機能を選んだ理由は、ユーザーが煩雑なプロセスを経ずに直接ウェブサイトでweb3ウォレットを持てるようにするためです。
類似の機能を提供する他のサービスとしては、以前のブログで取り上げています。
2.1.2 Engine
Engineはユーザーのガスレストランザクションを実現するトランザクション代行サービスです。このサービスを選んだのは、開発初心者でも手軽にNFTの発行を行えるためです。
類似の機能を提供する他のサービスには、Openzeppelin DefenderやunWalletなどがあります。thirdwebとOpenzeppelin Defenderの連携はこちらが参考になります。
2.1.3 Drop(ERC1155)
Dropはユーザーが独自のNFTを簡単に作成できるサービスで、ERC1155規格に基づいています。これを選んだ理由は、柔軟性と多機能性を兼ね備えているためです。
類似のサービスとしては、Manifold、Chocofactory、Bueno、NFT Garden、NFTマーケットプレイス各種など多くのサービスがあります。
2.1.4 React SDK
thirdwebは、さまざまな開発フレームワークや環境に対応するSDKを提供しています。本ブログでは、Webフロントエンド開発のためにReact SDKを選択しました。thirdwebはReactの他にも、TypeScript、React Native、Unity、Solidity、.NETといった言語やフレームワーク用のSDKを提供しています。
2.2 その他
その他、以下の開発ツールやフレームワークを利用していますが、本ブログではインストール方法や基礎的な利用方法は割愛します。Docker Desktopはthirdweb Engineをホストするために使用しましたが、課金すればthirdweb側でのホスティングで代替できます。
- Docker Desktop
- Visual Studio Code
- npm
- React.js
3. 実装詳細
本題の実装内容については、以下のステップで行いました。順に手順を解説します。
3.1 配布用のNFTコントラクトの作成
3.2 トランザクションプロバイダの構築
3.3 NFT配布用のバックエンドウォレットの作成
3.4 Webページの作成
3.5 実行
3.1 配布用のNFTコントラクトの作成
初めに、NFTをユーザーに配布するためのコントラクトを作成します。このコントラクトはthirdwebのプラットフォームを通じて簡単に作成可能です。
3.1.1 thirdwebへウォレットでログイン(or 新規作成)
thirdwebのContract作成画面で、[Connect Wallet]を行います。 (※ログインが求められたら次の手順を先に実施してください。)
web3ウォレットを持っていない方はSocial Loginでアカウント作成。持っている方は任意の方法でログインします。
ログイン後、右上がConnect Walletからログイン後のUIに変化したら成功です。クリックすると詳細がみられます。
これだけでweb3ウォレットが作成できます。銀行口座開設に比べて簡単ですね。
割り振られた42桁の数字は0xから始まる16進数を示す数字でウォレットアドレスと呼ばれ、ブロックチェーン上で透明性のある一意のユーザーを示します。
3.1.2 コントラクトの作成、デプロイ
続いて、thirdwebダッシュボード>[Contracts]>[Explore]>Edition Dropをクリックします(今回はERC1155規格のコントラクトを作成します)。
[Deploy now]をクリックします。
Nameに任意の値を入れます。
Networkをクリックし、デプロイしたいブロックチェーンを選択します。今回はEthereumのテストネットワークである「Sepolia」を選択しました。その後、[Deploy Now]をクリックします。
Deployの完了を待ちます。本来、ブロックチェーンのトランザクションを実行する際は、署名画面での署名と少量のガス代が必要ですが、どちらも不要でした。おそらくテストネットのため、thirdwebプラットフォームが代行してくれたのではないかと推測してます。
※コントラクトをデプロイするためにガス代を要求された場合はこちらでマイニングして少量取得するのが早いと思います。
https://sepolia-faucet.pk910.de/
コントラクトの作成が完了しました。コントラクトにもコントラクトアドレスと呼ばれる42桁の数字が生成され、ブロックチェーン上のプログラムの住所を示すような役割を担います。
3.1.3 NFTの発行準備
続いて1つNFTを用意しましょう。左のタブ[NFTs]から[Single Upload]を選択します。
NFTのNameを入れ、[Lazy Mint NFT]をクリックします。NFTの画像は任意で挿入可能です。Lazy Mintでは画面で入力したNFTのメタデータ(設定情報)をブロックチェーンに書き込むための準備処理を行います。
Token ID: 0のNFTが用意されました。
クリックすると詳細画面が開くので、Token ID: 0のNFTを発行するための設定を行います。
[Claim Conditions]>[Add Phase]をクリックします。
[Public]をクリック
デフォルト設定のまま、[Save Phases]をクリック
Phaseが追加され、NFTの発行準備が完了しました。
配布用のNFTコントラクト作成と準備はこれで完了です。
3.2 トランザクションプロバイダの構築
次に、thirdweb Engineを用いてブロックチェーン上でトランザクションを代理で実行し、ユーザーにシームレスな体験を提供するトランザクションプロバイダを構築します。このプロバイダは第三者のサービスに依存せずに、すべてのトランザクションの代行が可能です。
Engineの実装はthirdwebのホスティング環境を利用する方法(有料)とセルフホスティングの方法(無料)があり、今回は大人の都合で(社内決済に時間を要したくなかったので)、Engineセルフホスト手順に沿って、ローカル環境にEngineを無料でホスティングしました。
検証環境ではセルフホスティングでも問題ありませんが、本番運用を考える場合は、冗長性やセキュリティ対策が不可欠のため、UIの簡単操作で構築が可能な有料のthirdwebホスティングを利用することをお勧めします。
3.2.1 Postgresコンテナの立ち上げ
まず、ターミナルで以下コマンドを実行し、PostgresコンテナをDocker上に起動します。
docker run -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres
DockerでPostgresコンテナが起動していることを確認できました。
3.2.2 Engineコンテナの立ち上げ
続いて、以下のコマンドをターミナルで実行し、thirdweb EngineをDocker上で起動します。
docker run ` -e ENCRYPTION_PASSWORD="encryption_password" ` -e THIRDWEB_API_SECRET_KEY="①" ` -e ADMIN_WALLET_ADDRESS="②" ` -e POSTGRES_CONNECTION_URL="postgresql://postgres:postgres@host.docker.internal:5432/postgres?sslmode=disable" ` -e ENABLE_HTTPS=false ` -p 3005:3005 ` --pull=always ` --cpus="0.5" ` thirdweb/engine:latest
コマンド実行で必要となる①②は各自で取得します。
①についてはthirdwebダッシュボード>[Setting]タブ>[API Keys]タブ>[Create API Key]から取得可能です。
詳細画面で[Secret Key]を確認できます。
②については右上のウォレットアドレスを入力しましょう。
①②を入力し、ターミナルで実行します。
Dockerで2つ目のコンテナが起動していることを確認できました。
以下のログが表示されていれば、サーバ実行中であることを確認できます。
Listening on https://localhost:3005. Manage your Engine from https://thirdweb.com/dashboard/engine.
3.2.3 Engineコンテナをthirdwebダッシュボードに登録
続いて、立ち上げたコンテナをthirdwebダッシュボードに登録します。
thirdwebダッシュボード>[Engine]タブ>[Manage]タブ>[Import]でhttp://localhost:3005を入力します。
初回アクセス時には以下の手順が必要です:
- ブラウザでhttp://localhost:3005/jsonを入力してアクセスします。
- 「あなたの接続はプライベートではありません」という警告ページが表示されます。
- [詳細を表示]をクリックし、[ローカルホストに進む (安全ではありません)]を選択します。
これによりJSONファイルが表示され、ブラウザがローカルのEngineインスタンスに接続できます。
Dockerとうまく連携できていれば、詳細画面を開くことができます。
これでトランザクションプロバイダの構築は完了となります。
3.3 NFT配布用のバックエンドウォレットの作成
続いてNFTの取得をガスレスで実行するために配布用のバックエンドウォレットを作成します。
3.3.1 バックエンドウォレットの作成
thirdwebダッシュボード>[Engine]>[Manage]>[登録したインスタンス]>[Overview]でネットワークを「Sepolia」に変更し、[Create]をクリックします。
バックエンドウォレットが作成されました。
3.3.2 ガス代の準備
バックエンドウォレットに少量のガス代を入れてあげましょう。
※保有するtoken(SepoliaネットワークのEthereum token)がない場合は、こちらで少量マイニングを行いログイン時に作成したウォレットアドレスに補充しましょう。
https://sepolia-faucet.pk910.de/
右上のログイン時に作成したウォレットアドレスをクリック
Sendから送付先であるバックエンドアドレスを入力し、送付します。0.01もあれば十分です。
tokenの送付が完了しました。
これでNFT配布用のバックエンドウォレットの作成と準備は完了です。
3.4 Webページの作成
次に、サービスのフロントエンドとなるWebページを作成します。今回はthirdwebのReact SDKを使用しています。
3.4.1 サンプルコードのクローンと依存関係のインストール
まず、thirdwebのGitHubより、サンプルコードをクローンします。
https://github.com/thirdweb-example/engine-minting-api
クローンしたルートディレクトリに移動し、[npm install]で依存関係をインストールします。
[npm install encoding]と[npm install pino-pretty]も同様に実行してください。
3.4.2 evnファイルの準備
以下のとおり「.env.local」ファイルを作成し、クローンしたフォルダへ配置します。①~④は各自で取得しましょう。
ENGINE_URL="http://localhost:3005" THIRDWEB_CLIENT_ID="①" NEXT_PUBLIC_THIRDWEB_CLIENT_ID="①" THIRDWEB_SECRET_KEY="②" BACKEND_WALLET_ADDRESS="③" NFT_CONTRACT_ADDRESS="④" NEXT_PUBLIC_NFT_CONTRACT_ADDRESS="④"
①②はthirdwebダッシュボード>[Setting]タブ>[API Keys]タブ>[Create API Key]から取得可能です。
詳細画面で①②を確認できます。
③は先ほど作成したバックエンドアドレスを入力してください。
④は先ほど作成したNFTのコントラクトアドレスを入力してください。
3.4.3 ソースコードの編集①(page.tsx)
クローンしたソースコードの中で、[src]>[app] フォルダ内の page.tsx ファイルを以下のように編集します。この編集で、ログイン認証のユーザーインターフェースをカスタマイズし、ユーザーのNFT保有情報を表示する機能を追加しています。
"use client"; import { ConnectWallet, ThirdwebProvider, useAddress, inAppWallet, ja, useContract, useNFTBalance, } from "@thirdweb-dev/react"; export default function Home() { return ( <ThirdwebProvider activeChain="sepolia" clientId={process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID} locale={ja()} supportedWallets={[ inAppWallet({ auth: { options: ["google"], }, }), ]} > <ClaimPage /> <Component /> </ThirdwebProvider> ); } function ClaimPage() { const userWalletAddress = useAddress(); const onClick = async () => { const resp = await fetch("/api/claim", { method: "POST", body: JSON.stringify({ userWalletAddress }), }); if (resp.ok) { alert(`🎉 A reward has been sent to your wallet: ${userWalletAddress}`); } }; return ( <main className="flex flex-col gap-y-8 items-center p-24"> <h2 className="text-4xl font-extrabold"> NFT EngineAirDrop Demo </h2> <ConnectWallet theme={"dark"} btnTitle={"wallet接続"} modalTitle={"demo test"} switchToActiveChain={true} modalSize={"compact"} welcomeScreen={{ title: "Welcome Screen Title", subtitle: "Welcome Screen SubTitle", img: { src: "https://raw.seadn.io/files/6b9077cdd2b43af0bef05fb98c63369a.png", width: 150, height: 150, }, }} modalTitleIconUrl={""} showThirdwebBranding={false} /> {userWalletAddress && ( <button className="bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded-lg" onClick={onClick} > ✨Get NFT </button> )} </main> ); } function Component() { const userWalletAddress = useAddress(); const { contract, isLoading: isLoadingContract } = useContract(process.env.NEXT_PUBLIC_NFT_CONTRACT_ADDRESS); const tokenId = 0; const { data: ownerBalance, isLoading: isLoadingBalance, error } = useNFTBalance(contract, userWalletAddress, tokenId); if (isLoadingContract || isLoadingBalance) { return ( <div className="flex flex-col p-16 items-center font-extrabold"> <p>Loading...(接続後に保有枚数を表示します)</p> </div> ); } if (error) { const errorMessage = error.message ? error.message : "An unknown error occurred"; return <div>Error: {errorMessage}</div>; } // すべてが読み込まれたら、所有者の残高を表示 return ( <div className="flex flex-col gap-y-4 items-center font-extrabold"> <p>NFT獲得まで約1分程度かかります</p> <p>Wallet Address: {userWalletAddress}</p> <p>Balance: {ownerBalance ? ownerBalance.toString() : "N/A" }</p> </div> ); }
※ログインインターフェイスのカスタマイズについてはこちらを参考にしました。
※コントラクト情報の取得はthirdwebダッシュボード>[Contract]タブ>[作成したコントラクトの詳細画面]>[Code Snippets]タブを参考にソースコードへ組み込みました。
3.4.4 ソースコードの編集②(route.ts)
続いて、クローンしたソースコードの中で、[src]>[app]>[api]>[claim] フォルダ内にある route.ts ファイルを以下のように編集します。この変更により、作成したNFTコントラクトからclaim APIを呼びだすように設定しています。
import { NextResponse } from "next/server"; const { BACKEND_WALLET_ADDRESS, NFT_CONTRACT_ADDRESS, ENGINE_URL, THIRDWEB_SECRET_KEY, } = process.env; export async function POST(request: Request) { if ( !BACKEND_WALLET_ADDRESS || !NFT_CONTRACT_ADDRESS || !ENGINE_URL || !THIRDWEB_SECRET_KEY ) { throw 'Server misconfigured. Did you forget to add a ".env.local" file?'; } const { userWalletAddress } = await request.json(); const resp = await fetch( `${ENGINE_URL}/contract/sepolia/${NFT_CONTRACT_ADDRESS}/erc1155/claim-to?simulateTx=false`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${THIRDWEB_SECRET_KEY}`, "x-backend-wallet-address": BACKEND_WALLET_ADDRESS, }, body: JSON.stringify({ receiver: userWalletAddress, tokenId: "0", quantity: "1", }), } ); if (resp.ok) { console.log("[DEBUG] ok", await resp.json()); } else { console.log("[DEBUG] not ok", await resp.text()); } return NextResponse.json({ message: "Success!" }); }
※Engineを経由したコントラクトへのNFT発行指示はthirdwebダッシュボード>[Engine]タブ>[登録したインスタンスの詳細画面]>[Exploer]タブ>[ERC1155]を参考にソースコードへ組み込みました。
以上でWebページの作成は完了です。
3.5 実行
ここまで準備が完了したら[npm run dev]コマンドで動かしてみましょう。
このような画面が表示されると思いますので、ウォレット接続をしてみましょう。
ログインインターフェイスはシンプルにしています。Googleでサインイン。
任意のGoogleアカウントで今回作成したデモアプリ専用のウォレットを作成しましょう。
画面上の方で新規にweb3ウォレットアドレスが生成されました。
このweb3ウォレットはデモアプリのSNSログインを通じてアクセス可能です。他のweb3アプリケーションへの接続や他のweb3ウォレットアプリケーションへ持ち出す方法もありますが、今回は実装していません。
画面上の赤枠部分をクリックすると、基本的な操作(tokenの送付など)が可能です。
画面下の方では接続ウォレットアドレスと保有NFTの枚数が正しく表示されています。
NFTの取得をしてみましょう。
ポップアップでNFT獲得のメッセージが表示されます。
数秒後に画面をリロードしてみると、Balanceがアップデートされました。
Engineの方をみてみると、トランザクションが1件発生しているのがわかります。バックエンドウォレットからはガス代が引かれ、ユーザーはガスレスでNFTを獲得できています。
ブロックチェーンの取引を誰でも参照できるブロックエクスプローラにて同様に取引のログが確認できました。
トランザクションの詳細画面では TokenID: 0 のNFTが0xF6...のアドレスへ1枚転送されていることがわかります。
これで実装はすべて完了です。お疲れ様でした!🎉
4. 総括
今回はthirdwebサービスを組み合わせてweb3サービスを構築してみました。
DockerやReact、thirdwebAPIなど初めて触れる内容で苦労する点はありましたが、今回のテックスタックに馴染みの少ない自分でも一通り構築することができ、開発の容易性を実感することができました。
4.1 類似サービスでの構築
今回はthirdwebサービスを利用した構築を行いましたが、他のソリューションでも同様にNFT配布システムが構築可能であり、ベストプラクティスの模索やメリット比較のために検証する価値は十分にあるかと思います。気になるソリューションは下記しておきます。
- Web3Authサービスでの構築
- thirdwebホスティング環境のEngine利用(有料)
- Engineに代替するOpenzepplinDefenderの利用
- thirdwebのAccount Abstraction機能を利用したガスレストランザクション など
4.2 今回実装したサービスがweb3に与える影響
今回実装したthirdweb ConnectはWallet as a Service(WaaS)を提供し、thirdweb Engineはガスレストランザクションを可能にするソリューションです。これらのサービスは、ユーザービリティを向上させ、web3エコシステムにおいて重要な役割を果たしています。これらのサービスモデルが広く採用されれば、次のような影響が期待できます:
- web3技術の普及
WaaSの利用により、技術的知識がないユーザーでもウォレットの作成・管理が容易になります。これはweb3技術へのアクセス障壁を大幅に低下させ、より多くの人々がこの技術を利用できるようにします。さらに、ウォレットの存在をユーザーに意識させずに済むUI/UXを構築可能で、ブロックチェーン技術の煩雑さを隠しつつその利点を活かしたシステムを構築できる点も大きなメリットです。 - ウォレット管理の強化
セキュリティ対策されたWaaSサービスを利用することで、個々のユーザーや企業はより簡単に、より安全にデジタルアセットの保管が可能となります。 - 多様なブロックチェーン展開
WaaSプロバイダーは、異なるブロックチェーンネットワークに対応しており、接続が容易であるため、ユーザーに対して複数のブロックチェーンサービスの展開が可能となります。 - イノベーションの促進
今後のWaaSサービスプロバイダに依存する話となりますが、WaaS企業が提供する機能やAPIの拡張により、開発者は新しいタイプのアプリケーションやサービスをより迅速に開発することが可能になります。これは、web3領域のイノベーションを加速し、多様なビジネスモデルの創出を促進する可能性を秘めています。
このように、WaaSはweb3の採用を広げる重要な推進力となり得るため、その発展と普及に期待をしています。
現在、電通総研はweb3領域のグループ横断組織を立ち上げ、web3およびメタバース領域のR&Dを行っております(カテゴリー「3DCG」の記事はこちら)。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! (電通総研の採用ページ)
執筆:@himeno、レビュー:@nakamura.toshihiro
(Shodoで執筆されました)