コラム

Cloudflare Workerを使って無料でDynamic NFTを作る

今回は、CloudflareWorkerを使って無料でDynamicNFTを作っていきます。

1時間に1回、日本の天気に応じて画像が変わり、気温に応じてプロパティが変わります。

https://testnets.opensea.io/ja/assets/sepolia/0x5cecc8fa3a133d904fdd6db61b47a0d172d67c82/1

はじめに

作り方を説明する前に、DynamicNFTとは?とかなぜCloudflareWorker使うの?って話を少し。

CloudflareWorkerは、個人的に好きだからってのが実は1番の理由だったり。

DynamicNFT?

DynamicNFTの定義があまりよく分かってないのですが、おそらく外部データに基づきメタデータや画像が変更されるNFTのことを指しているのだと思います。

ここでは、1時間ごとのリアルタイムな天気予報?天気情報?(気温、天気)に基づき、メタデータと画像が変化するものを作っていきます。

なぜCloudflareWorker?

Web3ぽくChainlinkを使ったり、もしくはGCPやAWSなど何でも良いのですが、今回は極力無料に抑えるためにCloudflareWorkerを採用しました。

CloudflareWorkerは、無料プランであってもRequests100,000 / dayまで処理することができるし、クレジットカードの登録なしに使えるので、十分かと思います。

https://developers.cloudflare.com/workers/platform/pricing

最低限のWAFは導入されていますが、今回の実装は、少しunsecureです。

全てのオリジンからのリクエストを許可していますので、プロダクトの規模などで必要に応じて対策を講じてくださいね。

どんなdynamicNFT?

1時間に1回外部の天気予報を取得して、Cloudflare WorkerKVというkey-valueストレージにデータ(気温と天気)を保存します。

次に、Cloudflare WorkerKVのデータを元に、NFT用のmetadataを返却します。

サクッと解説

コードはこちら

https://github.com/eggdragons/cloudflare-workers-dynamicNFT

まずは、index.tsを見てください。

export default {
  async fetch(
    request: Request,
    env: Env
    // ctx: ExecutionContext
  ): Promise<Response> {
    return handleRequest(request, env);
  },

  async scheduled(
    event: Event,
    env: Env
    // ctx: ExecutionContext
  ): Promise<void> {
    await updateMetadata(env);
    console.log("cron processed");
  },
};

fetchでは、外部からリクエストがあるとmetadataを返却します。

一方scheduledでは、1時間に1回外部の天気予報を取得して、Cloudflare WorkerKVというkey-valueストレージにデータ(気温と天気)を保存します。

このスケジュールした時間にスクリプトを実行できる機能をCloudflare Workers Cronと言います。

次にサクッとhandler.tsを覗いてみましょう。

テンプレートのように使いまわしているので不要な部分も多々ありますが、今回重要なのは、この部分。

// 一部省略
const router = Router({ base: "/api" });    

router.get("/:tokenId", async ({ tokenId }) => {
    return await getMetadata(tokenId, env.dynamic, env.IMAGE_PATH);
})

次のURLにアクセスすることで、メタデータが返却されるようになっています。

https://〇〇/api/:tokenId

tokenIdがパスパラメータになっているので、tokenId毎に異なるメタデータが返却されます。

返却されるmetadataはどうなっている?

metadataは次のロジックで返却されています。

  1. Cloudflare WorkerKVからtemperatureweatherを取得します。
  2. weatherの値に応じてprefixの値を決定。
  3. メタデータを作成
    • imageimagePath(arweaveの) + prefix + tokenId + ".png"
    • attributestemperatureweatherを埋め込み

src/api/getMetadata.tsを見てみましょう。

  const temperature = await kv.get("temperature", { cacheTtl: 3600 });
  const weather = await kv.get("weather", { cacheTtl: 3600 });

ここでは、Cloudflare WorkerKVからデータを取得しています。

  const data = {
    image: imagePath + prefix + tokenId + ".png",
    external_url: "",
    description: "This is a trial dynamic NFT using cloudflare workers",
    name: `dynamicNFT#${tokenId}`,
    attributes: [
      {
        trait_type: "temperature",
        value: temperature,
      },
      {
        trait_type: "weather",
        value: weather,
      },
    ],
    background_color: "",
    animation_url: "",
    youtube_url: "",
  };

ここでは、OpenSeaのMetadata Standardsに乗っ取ってmetadataを作成しています。

定期実行されるCronはどうなっているの?

定期実行されるCronは次のロジックで返却されています。

  1. weatherapiから天気の情報を取得します。
  2. Cloudflare WorkerKVtemperatureweatherを保存します。

src/cron/updateMetadata.tsを見てみましょう。

  const result = await fetch(
    `http://api.weatherapi.com/v1/current.json?key=${env.WEATHER_API_KEY}&q=tokyo`
  )
    .then((res) => res.text())
    .then((data) => {
      return JSON.parse(data);
    })
    .catch((error) => {
      console.error(error);
    });

ここでは、weatherapiを用いて、天気情報を取得しています。

  await kv.put("temperature", result["current"]["temp_c"]);
  await kv.put("weather", result["current"]["condition"]["text"]);

ここでは、取得したデータをCloudflare WorkerKVに保存しています。

作り方

まずは、下記のリポジトリをクローンしてください。

https://github.com/eggdragons/cloudflare-workers-dynamicNFT

クローンし終わったら、必要なパッケージをインストールしていきます。

npm install

cloudflareを導入する

まずは、公式ページでアカウントを作成します。

https://www.cloudflare.com/ja-jp/

次に、Wranglerをインストールしていきます。ドキュメントはこちら

npm install -g wrangler

その次にターミナルからログインします。

npx wrangler login

workerKVを使う準備をしていきます。

npx wrangler kv:namespace create "dynamic"

すると、

{ binding = "dynamic", id = "*****" }

と帰ってくるので、これをwrangler.tomlファイルに次のように記載します。

kv_namespaces = [{ binding = "dynamic", id = "*****" }]

テスト用のworkerKVを作る場合は、

npx wrangler kv:namespace create "dynamic" --preview

returnにprewiew_idが書いてあるので、wrangler.tomlファイルに次のように更新します。

kv_namespaces = [{ binding = "dynamic", id = "*****", preview_id = "*****" }]

シークレットでない環境変数の設定

ついでに、wrangler.tomlファイルを見ると

ALLOW_ORIGIN = "*"
IMAGE_PATH = "https://arweave.net/D9Ac64K5hIaSqRvni5nSZjDAyqoetrAcDLGiOzLkNcc/"

と記載があります。ここでは、シークレットでない環境変数を保存しています。

IMAGE_PATHには、画像を保存しているフォルダのpathを記載しています。

weatherapi

次に天気予報の情報を取得していきましょう。今回は、weatherapiを使用します。

https://www.weatherapi.com/

シークレット環境変数の設定

新規アカウントを取得し、API KEYを入手します。入手できたら、コンソールより環境変数を設定していきます。

シークレットの環境変数は、下記のコマンドを使用します

npx wrangler secret put WEATHER_API_KEY

Enter a secret value:というのが表示されたら、先ほど入手したAPIKEYを入力します。

正常に処理が終わると、Success! Uploaded secret WEATHER_API_KEYと表示されます。

ブラウザでの環境変数の設定

ここで、一度cloudflareのサイトを確認してみましょう。

cloudflareのサイトにいき、workers & Pages > KV をクリックします。

すると、写真のようにKV storageが出来ている事が確認できるかと思います。

次に、workers & Pages > 概要 をクリックしてください。

dynamic > 設定 > 変数 とクリックしてもらうと、次の画面になります。

この画面から環境変数を設定することも可能です。

通常のテスト

次に、テストを行なってみましょう

.dev.varsファイルを作成し、テスト用の環境変数を設定していきます。

その作業が終わったら、次のコマンドを実行します。

npx wrangler dev

色々と注意書きが出てくるので、例えば、Add one to your wrangler.toml file:compatibility_date = "2023-08-10"と表示されたら、

wrangler.tomlファイルに

compatibility_date = "2023-08-10"

を書き足してください。

テスト環境が整ったので、早速、テストしましょう。別のコンソールを立ち上げ

curl "http://localhost:8787/api/1"

を入力すると、

{"image":"https://arweave.net/D9Ac64K5hIaSqRvni5nSZjDAyqoetrAcDLGiOzLkNcc/Rain1.png","external_url":"","description":"This is a trial dynamic NFT using cloudflare workers","name":"dynamicNFT#1","attributes":[{"trait_type":"temperature","value":null},{"trait_type":"weather","value":null}],"background_color":"","animation_url":"","youtube_url":""}

このようにレスポンスが帰ってくるのがわかります。

定期実行するCronの設定

次に、定期実行するCronの設定を行なっていきます。

wrangler.tomlファイルを開くと、

[triggers]
crons = ["0 */1 * * *"]

と記載があるかと思います。これが定期実行する際のトリガーの設定になっています。

この設定は、ブラウザからやるのがわかりやすいです。

dynamic > トリガー とクリックしてもらい、Cron トリガーからCron トリガー追加を押します。

ここで好きな時間を設定して、Cronに表示される内容をwrangler.tomlファイルに記載すると完成です!

定期実行するCronのテスト

今度は、定期実行するCronのテストを行なっていきます。

次のコマンドで、テスト環境を立ち上げて

npx wrangler dev --test-scheduled

別のコンソールから次のコマンドを投げます。

curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"

すると、Ran scheduled eventと帰ってくると思います。(Ran???)

vitestによるテスト

vitestによるテストも記載しています。

index.test.tsを開いてもらうと内容がわかるかと思います。

次のコマンドでテスト起動となります。

npm run test

※weatherapiの結果をraw testで埋め込んでいるので、適宜内容変更してください。

デプロイ

デプロイしていきましょう

npx wrangler publish

最後にNFTのtokenURIの設定

NFTのtokenURIには、https://dynamic.eggdragondev.workers.dev/api/を使用します。

完成!

1時間に1回、日本の天気に応じて画像が変わり、気温に応じてプロパティが変わります。

https://testnets.opensea.io/ja/assets/sepolia/0x5cecc8fa3a133d904fdd6db61b47a0d172d67c82/1

いかがだったでしょうか?他にも色々無料でできる方法を模索しましたが、この方法が一番汎用性が高いと思ったのでご紹介しました!

-コラム