Amazon Managed Blockchain で Simple NFT Marketplace を触ってみた

# はじめに

[前回の記事](https://qiita.com/sugimount-a/items/8bb29df758a741b2cfe7)では、Amazon Managed Blockchain で Ethereum Node を作り、簡単な動作確認を行いました。今回は、独自のスマートコントラクトをデプロイする方法を学んでいきます。

aws-samples に、「Simple NFT Marketplace」という名前の GitHub リポジトリがあり、ここでサンプルコードが公開されています。日本語の説明もあり、わかりやすいのでこれを試していきたいと思います。
https://github.com/aws-samples/simple-nft-marketplace



この「Simple NFT Marketplace」を実施することで、以下の点が学習できます

- スマートコントラクトをデプロイする
  - Amazon Managed Blockchain でつくった Ethereum Node を使って Goerli ネットワークにデプロイする
- デプロイしたスマートコントラクトを、Lambda から呼び出す
- フロントエンドは、API Gateway と Lambda を経由して、スマートコントラクトを利用する



# システム構成図

こんな感じのシステム構成を作っていくリポジトリになっています。シンプルな NFT ベースのマーケットプレイスの内容となっています。それではすすめていきましょう。

![image-20220506200243263.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0e964ed0-7192-0f65-d49f-2a2db36de58c.png)



# Amazon Managed Blockchain で Ethereum Node の準備

aws-samples で公開されている GitHub のリポジトリを clone します。

```
git clone https://github.com/aws-samples/simple-nft-marketplace.git
```



[前回の記事](https://qiita.com/sugimount-a/items/8bb29df758a741b2cfe7)で作成した、Goerli ネットワークの Ethereum Node を使っていきます。

![image-20220506202509699.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4bc9245a-b542-65ba-d83f-d9c5eebdb8df.png)



2つの Endpoint はを後の手順で使うため、メモをしておきます。

![image-20220506202710846.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/8dd04604-4be5-e48f-fa50-72bc3796cc32.png)



# Contract のデプロイ

Ethereum では、スマートコントラクトとして独自のプログラムコードを動かすことが出来ます。Ethereum の場合は、[Solidity](https://solidity-jp.readthedocs.io/ja/latest/)と呼ばれる言語でスマートコントラクトのコードが書かれています。

今回は、リポジトリの中に Solidity で書かれているプログラムコードが準備されているので、これを使ってデプロイを進めていきます。Solidy で書かれているスマートコントラクトのコードはこちらです。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/contract/contracts/SimpleERC721.sol



`contract`ディレクトリに移動します。

```
cd simple-nft-marketplace/contract/
```



必要なモジュールをインストールします

```
npm install
```



npm install で定義されている依存関係はこちらです。hardhat ツールも定義されています。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/contract/package.json



hardhat を使って、Solidity で書かれたコードをコンパイルします。`artifacts` と `cache` というディレクトリが作成されたら完了です。

```
npx hardhat compile
```



hardhat コマンドを使って、Amazon Managed Blockchain で作った Ethereum Node を使ってスマートコントラクトをデプロイします。Amazon Managed Blockchain の Node を使ってデプロイするときには、AWS が提供している仕組みの「Signature Version 4 (SigV4)」の署名が必要です。

今回の構成の場合は、デプロイツールの hardhat コマンドをカスタマイズする `hardhat.config.js ` ファイルで、署名を行っている構成になっているようです。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/contract/hardhat.config.js#L33



環境変数に、Node の HTTP Endpoint を指定します

```
export AMB_HTTP_ENDPOINT=nd-76uz24bmarcszofd6lvqm22eea.ethereum.managedblockchain.ap-northeast-1.amazonaws.com
```



続いて、Contract をデプロイする Owner のアカウントを作成します。以下のコマンドを実行してください。

```
npx hardhat account
```



実行例

```
> npx hardhat account
Address 0x75b57583CF3Ad7808C7659865aD1fE2c4EB723Aa
PrivateKey 0x111111secret111111
```



出力の `Address` はトークンの送付に必要になるアドレスで、`PrivateKey` は Transaction の署名に必要な秘密鍵です。`Address` は `PrivateKey` から生成されるので、1 対 1 の関係です。この `Address` と `PrivateKey` は後続の手順で必要になるので、必ずどこかに転記しておいてください。



Contract を Testnet である Goerli にデプロイするにあたって、手数料の Gas を支払う必要があります。そのため、上記で作成した Address に ETH を付与する必要があります。Goerli はテストネットなので、動作確認をしやすくするために、ETH を付与しているサイトがあります。たとえば 次の URL にアクセスして、Address を入力すると 0.05 Ether を入手できます。

https://goerlifaucet.com/

![image-20220506204640035.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/e17227af-fba5-c382-93c1-60b7a7549b79.png)



環境変数として、先ほど作成した Account に紐づく秘密鍵を設定します

```
export PRIVATE_KEY=0x111111secret111111
```



引き続き、Amazon Managed Blockchain に接続するための AWS IAM の権限設定を行います。新規で IAM ユーザーを作成する場合は、`AmazonManagedBLockchainFullAccess` ポリシーをアタッチしてください。AWS Access Key ID と AWS Secret Access Key を以下のように環境変数に設定します。

```
export AWS_ACCESS_KEY_ID=xxxxxxxxx
export AWS_SECRET_ACCESS_KEY=yyyyyyyyy
export AWS_DEFAULT_REGION="ap-northeast-1"
```



以下のコマンドで、コントラクトをデプロイします。`--network amb` と指定しているので、正しく署名が行われている形です。

```
npx hardhat run --network amb scripts/deploy-amb.js
```



実行例

- Goerli ネットワークにデプロイしたスマートコントラクトに、アドレスが付与されています。
- `0x07eCA4Fdda923601351F3D7989d13Ea2B9125acf`

```
> npx hardhat run --network amb scripts/deploy-amb.js
Adding https:// prefix to AMB_HTTP_ENDPOINT
Adding https:// prefix to AMB_HTTP_ENDPOINT
Contract deployed at 0x07eCA4Fdda923601351F3D7989d13Ea2B9125acf
```



Etherscan のページ上で、Goerli ネットワークのアドレスに紐づく Contract の情報を確認可能です。Contract Create のイベントを確認できます。

https://goerli.etherscan.io/address/0x75b57583CF3Ad7808C7659865aD1fE2c4EB723Aa

![image-20220508154317085.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0a0c9ce8-d954-1a49-e3a4-62cda73a2314.png)



# Lambda のデプロイ

スマートコントラクトをデプロイすることが出来たので、それを呼びだす Lambda 周辺の AWS サービスをデプロイしていきます。

以下のディレクトリに移動します。

```
cd simple-nft-marketplace/provision/
```



環境変数で利用するリージョンを設定します。

```
export AWS_DEFAULT_REGION="ap-northeast-1"
```



必要なモジュールをインストールします

```
npm install
```



`/lambda` ディレクトリでも同様にモジュールのインストールが必要です。以下のコマンドを実行してください。

```
cd lambda; npm install; cd -;
```



続いて、Contract をコンパイルした生成物 (前の手順で作成したもの) を `/lambda/contracts` にコピーします。以下のコマンドを実行してください。

```
cp ../contract/artifacts/contracts/SimpleERC721.sol/SimpleERC721.json lambda/contracts/.
```



続いて、CDK プロジェクトをビルドします。以下のコマンドを実行してください。

```
npm run build
```



続いて、必要な環境変数を設定します。Amazon Managed Blockchain のエンドポイントとデプロイした Contract のアドレスを設定します。

```
export AMB_HTTP_ENDPOINT=https://nd-76uz24bmarcszofd6lvqm22eea.ethereum.managedblockchain.ap-northeast-1.amazonaws.com
export CONTRACT_ADDRESS=0x07eCA4Fdda923601351F3D7989d13Ea2B9125acf
```



最後に、デプロイを実施します。以下のコマンドを実行してください。自分の環境では、約 6 分ほどかかりました。

```
cdk deploy SimpleNftMarketplaceStack
```



自動デプロイされた AWS サービスの主要なものを抜粋

- Lambda Functions
- DynamoDB
- API Gateway
- S3 Bucket
- Cognito



実行例

```
 ✅  SimpleNftMarketplaceStack

✨  Deployment time: 347.71s

Outputs:
SimpleNftMarketplaceStack.NftApiEndpoint = https://uqtnh9wj8i.execute-api.ap-northeast-1.amazonaws.com/prod/
SimpleNftMarketplaceStack.NftApiEndpoint8C6C6AD5 = https://uqtnh9wj8i.execute-api.ap-northeast-1.amazonaws.com/prod/
SimpleNftMarketplaceStack.UserPoolClientId = secret
SimpleNftMarketplaceStack.UserPoolId = ap-northeast-1_Tkv3OnQDj
```



## Lambda がどのように Contract を呼びだしているのか

Lambda のデプロイが出来たので、どのように Lamnda Function が Ethereum のコントラクトを呼びだしているのかを見ていきます。Ethereum は勉強中のため、誤りが無いように気を付けていますが、もし誤りがあったらすいません。

まず、スマートコントラクトを `npx hardhat compile` のコマンドでコンパイルしたときに、`/contract/artifacts/contracts/SimpleERC721.sol/SimpleERC721.json` のファイルが生成されました。これは、コントラクトがどのようなメソッドや引数を持っているのか、インターフェースを定義しているファイルになっています。このファイルは、Contract Application Binary Interface(ABI) に関連するファイルになっています。ABI については、次の Blog がわかりやすいです。
https://y-nakajo.hatenablog.com/entry/2019/05/14/182854

Lambda Function の共通的なライブラリとして、`context.ts` ファイルがあります。このファイルの中で、コンパイル時に生成された `SimpleERC721.json`ファイルを読み込んでいます。それが次のコードの部分です。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/provision/lambda/lib/context.ts#L6

その後に、ABI を使ったオブジェクトを生成しています。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/provision/lambda/lib/context.ts#L33

実際に Ethereum ネットワークにトランザクションを流す関数はこちらです。この関数の中で、先ほどの `contest.ts` を import しています。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/provision/lambda/purchaseJob.ts#L1

`const tx = contract.methods.purchase(paramsJob.tokenId);` のコードで、スマートコントラクトのメソッドである `purchase` を呼びだしています。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/provision/lambda/purchaseJob.ts#L18

その `purchase` メソッドは、Solidy 言語によって以下のコードがあり、このインターフェースを使って呼び出しています。
https://github.com/aws-samples/simple-nft-marketplace/blob/main/contract/contracts/SimpleERC721.sol#L62



かなり細かい部分になるので、また興味のある時に見直してもらえると良いと思います。



# フロントエンドの動作確認

スマートコントラクトと、それを呼びだす Lambda などを準備できました。次にフロントエンド側を用意していきます。以下のディレクトリに移動します。

```
cd simple-nft-marketplace/marketplace/
```



ヒアドキュメントを使って、以下のファイルを作成します。環境に合わせて値を変更してください。

```
cat <<EOF > .env.local
VUE_APP_AWS_REGION=ap-northeast-1
VUE_APP_API_ENDPOINT=https://uqtnh9wj8i.execute-api.ap-northeast-1.amazonaws.com/prod
VUE_APP_USER_POOL_ID=ap-northeast-1_Tkv3OnQDj
VUE_APP_USER_POOL_WEB_CLIENT_ID=secret
EOF
```



続いて、以下のコマンドを実行して、必要なモジュールをインストールします。

```
npm install
```



最後に、以下のコマンドを実行して、ローカルでフロントエンドを起動テストします。`localhost:8080` でリッスンします。

```
npm run serve
```



ブラウザの [http://localhost:8080](http://localhost:8080/) にアクセスしてください。サイトが表示されていれば成功です。

![image-20220506213025604.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/220a2833-102b-1f5b-87f6-395ea60f36e2.png)



Create account で、Cognito のユーザーを作成します

![image-20220506213106628.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/84934228-47c7-c071-1761-913d7fbecccb.png)



メールに code が届くので入力

![image-20220506213308389.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/bed55bde-be37-34e5-8a1c-dd729d7e48fb.png)



右上の Account を押す

![image-20220506220058719.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/e515a369-5d17-d4ae-73cd-f6c06ee31881.png)



新たに作成したユーザーに、Goerli ネットワークのアカウントが作成されています。

![image-20220506220137984.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/74cd6fad-4fd5-7218-8d49-69ca8e3f08e4.png)





# フロントエンドのデプロイ

ローカルで動作確認が出来たので、このフロントエンドを CloudFront + S3 にデプロイします。以下のディレクトリに移動します。

```
cd simple-nft-marketplace/marketplace/
```



build

```
npm run build
```



さらに、以下のディレクトリに移動します。

```
cd simple-nft-marketplace/provision/
```



環境変数を設定

```
export AWS_DEFAULT_REGION="ap-northeast-1"
```



フロントエンドをデプロイします。

```
cdk deploy SimpleNftMarketplaceFrontendStack
```



実行例

- CloudFront の URL が Output に表示されます。

```
Outputs:
SimpleNftMarketplaceFrontendStack.CfnEndpoint = https://yoursite.cloudfront.net

✨  Total time: 325.95s
```



実際にアクセスしてみると、正常にフロントエンドが表示されます。

![image-20220506220058719.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/34935c72-efa2-2b7d-f5b5-ca6a9c8b235e.png)



# NFT の作成

デプロイしたフロントエンドをつかって、NFT を作成することができます。ただ、作成するためには ETH が必要です。初期状態では、Balance (ETH) が 0 なので、新たに付与します。Account ページで、その Account の Address をメモっておきます。

![image-20220506220137984.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d0f7ef97-7c8e-fbea-2e9c-87d289d77be7.png)



再び以下サイトで付与できます。
URL : https://goerlifaucet.com/



0.05 Eth が付与されました。

![image-20220508123619605.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/e09af86e-60a4-4061-bca5-dc10874d7c44.png)



NFT の作成として、Create NFT を押します。

![image-20220508133810616.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d7abdae3-4d99-6e3c-0dcf-2d310b61fc7c.png)





以下のパラメータを入れて、Create を押します。

- NFT として紐づける画像をアップロード
- NFT のトークンとしてのタイトルと説明を入れる

![image-20220508134039817.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/aec0e06a-2641-c7c2-2c4e-44cd429a754d.png)



NFT が作成できました。この NFT に対して、値段も付けられます。

![image-20220508134209791.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/bcaa6ec6-6ba0-e004-323d-9dbbac52fa99.png)



NFT を作成したことにより、Accout の ETH が減っています。

![image-20220508134306869.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/2b84bc50-6e84-be05-5006-dbc0a394dbff.png)



# 検証を通じてわかったこと

- フロントエンドから直接 Amazon Managed Blockchain の Ethereum Node にリクエストを投げるのは推奨されていないため、Lambda などのコンピュートリソースを間に挟むことを検討すると良い。

  - 以下の AWS Document にも記載があり

  - https://docs.aws.amazon.com/ja_jp/managed-blockchain/latest/ethereum-dev/ethereum-json-rpc.html#supported-json-rpc

  - > The Signature Version 4 signing process requires credentials associated with an AWS account. Some examples in this section export these sensitive credentials to the shell environment of the client. Only use these examples on a client running in a trusted context. Do not use these examples in an untrusted context, such as in a web browser or mobile app. Client credentials should never be embedded in user-facing applications. To expose an Ethereum node on Managed Blockchain to anonymous users visiting from trusted web domains, you can set up a separate endpoint in Amazon API Gateway backed by a Lambda function that forwards requests to your node using the proper IAM credentials.

- スマートコントラクトは、Amazon Managed Blockchain を介して、Ethereum Netwotk にデプロイが出来る

  - デプロイしたスマートコントラクトにもアドレスが付与されており、ネットワーク上で利用可能となる

- スマートコントラクトを外部のプログラムから呼び出す際には、Contract Application Binary Interface(ABI) を利用して呼び出す

  - `contract.methods.purchase(paramsJob.tokenId);` 
    - こんなかんじ
  - ABI とは
    - https://y-nakajo.hatenablog.com/entry/2019/05/14/182854



# 参考URL

Goerli Etherscan
https://goerli.etherscan.io/

AWS Samples
https://github.com/aws-samples/simple-nft-marketplace