Amazon Web Services ブログ

Amazon Managed Blockchain でフルスタックサーバーレス NFT アプリケーションを開発する – Part 2

このブログは、Rafia Tapia によって書かれた Develop a Full Stack Serverless NFT Application with Amazon Managed Blockchain – Part 2 を翻訳したものです。

このブログは、以前のブログで解説した非代替性トークン (NFT) 開発環境のセットアップ後の内容になっています。そのため、セットアップが完了していることが前提の内容になっています。パート 1 では、開発環境のセットアップ方法について説明しました。この記事では、Amazon Managed BlockchainAmazon API GatewayAWS Lambda を使用した、NFT マーケットプレイスアプリケーションについて説明しています。

ソリューション概要

このブログのコードサンプルは、パート 1 で紹介した以下のリファレンスアーキテクチャに基づいて作られています。

このリファレンスアーキテクチャを実現するにあたり、ブログ内で紹介されているサンプルコードは、3 つの機能モジュールで構成されています。

  • ShareToWinWeb – React と TypeScript で書かれたフロントエンドのウェブアプリケーション。NFT マーケットプレイスのユーザーインターフェイスを提供
  • ShareToWinRestAPI – React フロントエンドを、イーサリアムブロックチェーン上の NFT スマートコントラクトに接続するための REST API
  • ShareToWinContract – アプリケーションのコア機能を実行するために、イーサリアムブロックチェーン上にデプロイされる Solidity スクリプト、またはスマートコントラクトの実装

このブログでは、フルスタック NFT アプリケーションの実装方法を完全に理解できるように、各モジュールについて説明します。アプリケーションの全コードは、GitHub リポジトリ で公開されています。

ShareToWinWeb: React/TypeScript UI

GitHub リポジトリの ShareToWinWeb フォルダ内のコードは、フロントエンドアプリケーションです。React と TypeScript で書かれたこのアプリケーションは、REST API とやり取りするシングルページアプリケーション (SPA) で、NFT のマーケットプレイス機能を提供します。マーケットプレイスアプリケーションを稼働させるにあたり、このコードを Amazon Simple Storage Service (Amazon S3) にデプロイします。Amazon S3 は、業界トップクラスのスケーラビリティ、データの可用性、セキュリティ、パフォーマンスを提供するオブジェクトストレージサービスです。以下のスクリーンショットは、アプリケーションの画面を示したものです。

このアプリケーションには、マーケットプレイスの管理者と買い手の 2 種類のユーザーがいます。このサンプルアプリケーションは、マーケットプレイスにユーザーを追加する非常に単純な仕組みで構成されていますが、本番システムでは、Amazon Cognito のような AWS サービスを利用して、ユーザー管理機能を実装する必要があります。サンプルコードでは、買い手ユーザーは Ganache 開発ネットワークを開始したときに作成されるアカウントを指しています。管理者ユーザーは、画面上の Add Buyer を選択することで、Ganache ネットワーク上の既存アカウントをアプリケーションに関連付けることができます。

管理者と買い手は、Register New Asset を選択することで新しいアセットを登録することができます。このとき、アセット名、アセットに関する説明、アセットの初期価格 (Ether) など、いくつかの基本情報を収集するフォームが表示されます。この情報は REST API 関数 registerNewAsset に渡され、この関数がアセットに対して固有 ID を生成し、この ID をスマートコントラクトに渡します。スマートコントラクトの関数 registerAsset は新しい NFT を Mint (鋳造) し、アセットの固有 ID を NFT トークン ID と関連付けます。この REST API 関数はさらに、アセットの固有 ID、NFT トークン ID、および UI アプリケーションから送られたデータを、Amazon DynamoDB のテーブルに保存し、クエリできるようにします。DynamoDB は、ハイパフォーマンスなアプリケーションをあらゆる規模で実行するために設計された、フルマネージドでサーバーレスの key-value NoSQL データベースです。

アセットが登録され、NFT が Mint (鋳造) されると、NFT をマーケットプレイス上で売買できるようになります。次のスクリーンショットは、買い手視点のアプリケーション画面です。買い手は、2 つの異なるアセットに紐づくそれぞれの NFT を購入したり、買い手が所有する NFT を売却したりすることができます。

ウォレットと秘密鍵

NFT が売り手から買い手に譲渡されるとき、2つの事象が発生する必要があります。まず、スマートコントラクトが NFT の所有者アドレスを更新し、次に買い手と売り手のアカウントから Ether 残高を調整する必要があります。マーケットプレイスが買い手と売り手の Ether 残高を調整するためには、買い手と売り手の間で NFT の購入額に対して残高を移転するトランザクションを作成する必要があります。この移転を完了するには、このトランザクションに買い手の秘密鍵で署名する必要があります。そのため、NFT マーケットプレイスアプリケーションは、NFT マーケットプレイスアプリケーションにウォレット機能を提供するブラウザ拡張機能である MetaMask のようなウェブウォレットを統合する必要があります。

このサンプルアプリケーションでは、スマートコントラクトにエスクロー (第三者を介して支払いを行う仕組み) の概念を用いることで NFT の譲渡を容易にするといった、別のアプローチをとっています。このサンプルアプリケーションでは、マーケットプレイスから NFT を購入しようとする買い手は、NFT を実装したスマートコントラクトにエスクロー残高を預ける必要があります。エスクローを使用すると、エスクロー残高が NFT スマートコントラクトの一部となります。そのためスマートコントラクトは、NFT 譲渡に関わる 2 者のエスクロー残高を調整したり、エスクロー額から売り手に Ether を送ったりする機能を備えることになります。このアプローチでは、NFT アプリケーションが買い手の秘密鍵を知らなくても、NFT 取引額を売り手に送ることができます。エスクロー資金は、アプリケーションユーザーがアクセスできる MetaMask のような標準的なウォレットを使用してスマートコントラクトに送金することができます。スマートコントラクトにエスクロー資金を送金するには、ユーザーはコントラクトのアドレスが必要です。またスマートコントラクトへの資金送金をするために、任意のウォレットを使用することができます。

このサンプルアプリケーションでは、エスクロー方式に加え、トランザクション署名の機能も提供しています。この機能は、NFT アプリケーションが買い手の秘密鍵にアクセスできる場合に、買い手に代わって残高移転トランザクションを作成するものです。アプリケーション上で買い手がこの機能を使用すると、買い手とスマートコントラクトの間で残高移転トランザクションが発生し、MetaMask のようなウォレットがスマートコントラクトにエスクロー残高を送るのと同じような挙動をします。アプリケーションのユーザーから秘密鍵を取得するこのアプローチは、純粋に秘密鍵の統合を示すデモのためのものです。この方法は、本番環境のアプリケーションで使用するべきではありません。本番環境のアプリケーションでは、MetaMask のようなウェブ HD ウォレットとアプリケーションを統合することを検討する必要があります。

注:このサンプルアプリケーションで使用している秘密鍵キャプチャーの実装を本番環境で使用するのは、セキュリティのベストプラクティスに反します。本番環境の NFT マーケットプレイスでは、アカウントの秘密鍵を処理するためにウォレット機能をアプリケーションに組み込む必要があります。

ShareToWinRestApi: Javascript/Express REST API

GitHub リポジトリShareToWinRestApi フォルダに含まれるソースファイルは、フロントエンドアプリケーションとスマートコントラクトを接続する REST API プロジェクトです。フロントエンドアプリケーションがスマートコントラクト内の関数を実行するトリガーとなるものです。アプリケーションコードは JavaScript で書かれており、express.js を使用して、REST API ファイルをホストするローカル Web サーバーを作成します。

Amazon API GatewayAWS Lambda は、この機能を本番環境の NFT マーケットプレイスにデプロイするための最適な方法です。API Gateway は、開発者があらゆる規模の API を作成、公開、保守、監視、保護できるようにするフルマネージドサービスです。Lambda は、サーバーレスでイベント駆動型のコンピュートサービスであり、サーバーのプロビジョニングや管理を行わずに、あらゆるタイプのアプリケーションやバックエンドサービスのコードを実行することができます。

イーサリアムネットワーク上に配置されたスマートコントラクト機能を呼び出すために、Web3.jsEthers.jsという 2 つの人気のある JavaScript ライブラリが広く使用されています。本サンプルアプリケーションでは、Web3.js を使用しています。

Web3.js ライブラリには、イーサリアムネットワークに接続するために作成が必要な HttpProvider というオブジェクトがあります。サンプルコード内の contractProvider.js は、環境変数に基づいて、Ganache ネットワーク または Amazon Managed Blockchain 経由の Goerli、Ropsten、Rinkeby、Mainnet イーサリアムネットワークのいずれかのプロバイダーを作成する機能を提供します。Amazon Managed Blockchain (AMB) は、イーサリアムのようなパブリックネットワークとのやり取りを容易にする、フルマネージドサービスです。

スマートコントラクトとやり取りするために、この REST API プロジェクトは、デプロイされたコントラクトアドレスとコントラクトの ABI (Contract Application Binary Interface) といった 2 つの情報を持っている必要があります。以下は、registerAsset スマートコントラクト関数用に生成される ABI コードのサンプルです。

{
        "inputs": [
          {
            "internalType": "uint256",
            "name": "assetID",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "salePrice",
            "type": "uint256"
          },
          {
            "internalType": "string",
            "name": "_uri",
            "type": "string"
          }
        ],
        "name": "registerAsset",
        "outputs": [
          {
            "internalType": "uint256",
            "name": "newTokenID",
            "type": "uint256"
          }
        ],
        "stateMutability": "nonpayable",
        "type": "function"
}

イーサリアムネットワークにデプロイされる各コントラクトには、そのコントラクトをネットワークにデプロイする際に作成される一意のアドレスがあります。イーサリアムネットワーク上のスマートコントラクトは不変であるため、ネットワークに新しいコントラクトをデプロイすると、スマートコントラクト機能を呼び出すために REST API プロジェクトで設定する必要がある新しいコントラクトアドレスが生成されます。サンプルアプリケーションでは、このコントラクトアドレスを環境変数として保存し、REST API コードがスマートコントラクトと通信するために使用します。

イーサリアムブロックチェーンでは、スマートコントラクトの機能に必要なデータのみをチェーン上に保存し、イーサリアムネットワーク外の追加データは、他の外部データベースに保存することが推奨されています。このサンプルアプリケーションでは、その外部ストレージとして Amazon DynamoDB を使用しています。

ShareToWinContract: Solidity スマートコントラクト

このフルスタック NFT アプリケーションの最後のコンポーネントは、NFT 機能を実現するために ERC721 仕様を実装したスマートコントラクトです。スマートコントラクトのファイルは、ShareToWinContract フォルダの下にあります。 AssetToken.sol というファイルには、AssetToken NFT の実装が含まれており、OpenZeppelin SDK を使用しています。OpenZeppelin は、ERC721 や ERC20 を含む多くのイーサリアム仕様のベース実装を提供する、オープンソースのスマートコントラクト集です。先に説明したように、スマートコントラクトのコードをデプロイするための開発ネットワークとして Ganache を使用します。以下のコマンドを実行して、開発ネットワークを起動するとともに、公開鍵/秘密鍵をファイルに保存します。

npx ganache-cli --acctKeys ../ShareToWinRestApi/ethaccounts.json

上記のコマンドは、すべてのテストアカウントの公開鍵と秘密鍵を、ethaccounts.json というファイルに保存します。このファイルは REST API プロジェクトに配置され、アプリケーションで使用するアカウントの秘密鍵を取得するために利用されます。

Truffle 開発フレームワークを使用してこのスマートコントラクトをブロックチェーンネットワークにデプロイする場合、デプロイされたコントラクトアドレスを必ず記録しておいてください。このコントラクトアドレスは、REST API プロジェクト内の envExport.sh ファイルで環境変数 CONTRACTADDRESS を設定するために使用します。Truffle コンソールから migrate コマンドを実行すると、以下のようにコントラクトアドレスが表示されます。

AssetToken.sol ファイル内のスマートコントラクトコードに変更が加えられるたびに、スマートコントラクトをコンパイルして生成された ABI は REST API プロジェクトの contractAbi.js ファイルにコピーされる必要があります。スマートコントラクトのコンパイルの結果生成される ABI コードは、ShareToWinContract フォルダー下のbuildフォルダーに配置されます。スマートコントラクトと同じ名前の JSON ファイル (サンプルコードの AssetToken.json) には、コントラクトの ABI が含まれています。これにより、スマートコントラクトの関数のインターフェイスに変更があった場合に、REST API が認識できるようになります。

エスクロー資金を NFT スマートコントラクトに送信するには、ShareToWinRestApi フォルダー内の ethaccounts.json ファイルにあるテストアカウントの公開鍵と秘密鍵を使用します。

プロダクションレディの NFT アプリケーションを開発しテストしたら、Amazon Managed Blockchain (AMB) ノードを介してイーサリアムのメインネットにデプロイすることが可能です。フルマネージドサービスである Amazon Managed Blockchain (AMB) を利用すれば、わずか数クリックでイーサリアムネットワークに参加することができます。イーサリアムネットワークに接続された AMB ノードは、AWS Lambda などの他の AWS サービスと統合し、スマートコントラクト機能を呼び出して NFT の Mint (鋳造) と譲渡を行うことができます。

まとめ

このブログでは、Amazon Managed Blockchain でフルスタックサーバーレス NFT アプリケーションを開発する – Part 1 というブログの延長で、完全な NFT マーケットプレイスアプリケーションの 3 つのレイヤーについて説明しました。このブログで取り上げたコード全体は、GitHub で公開されています。この 2 部構成のブログでは、スマートコントラクトの開発環境を設定する方法と、NFT 取引を行う機能を持つマーケットプレイスアプリケーションを実装するための NFT スマートコントラクトを記述する方法について包括的に解説しました。
サンプルコードをお試しいただき、ご意見をいただけると幸いです。

このブログは、ソリューションアーキテクトの 木村 直登 が翻訳しました。原文はこちらをご覧ください。