ERC-721トークンのメタデータをFirebase上に置くために考えたこと

ERC-721では、トークンに紐付けるアセットのメタデータ（名前、説明、画像）をJSONに記述し、そのJSONファイルのURLを `tokenURI` としてスマートコントラクトに書き込むことを要求しています。

この仕様は以下のEIPに書いてあります。

https://eips.ethereum.org/EIPS/eip-721

JSONスキーマは以下のとおりです。

```json
{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents",
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents",
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive.",
        }
    }
}
```

このメタデータをFirebase上に置こうとしたのですが、トランザクションなどの都合上、いろいろと考えなければいけないことがあったので共有したいです。もっと良い方法があればコメントください。

### トークンのmintとメタデータの書き込みの順番について

「トークンのmint → メタデータの書き込み」の順番なのか、「メタデータの書き込み → トークンのmint」の順番なのか決めたいです。前提として、**悪意のあるユーザーがいない限り**、正常なtokenURIが書き込まれていないトークンが発生しないようにしたいです。

「**悪意のあるユーザーがいない限り**」というのは、tokenURIとしてデタラメな値を設定してトークンをmintするのは可能ですが、それは許容する（どうにもできない）という意味です。一方で、途中でネット回線が切断した場合などはフォローするようにします。

tokenURIはトークンをmintしたあとにsetTokenURIのようなメソッドを呼び出して設定することもできますが、トークンのmint後、setTokenURIを呼び出すまでに回線が切断されてしまうと、tokenURIが設定されていないトークンが出来てしまいます。よって、mintの引数としてtokenURIを要求して、mintのメソッド内でsetTokenURIを呼び出す方法のほうが良さそうです。

トークンのmint時にはtokenURIが必要ということになるので、トークンのmintよりも先にメタデータの書き込みを終わらせておく必要があります。tokenURIを決めておき、トークンのmint後にメタデータを書き込む方法もありますが、やはり回線が切断されてしまうと、トークンのメタデータにアクセスしに行っても404になってしまうトークンなどが出来てしまいます。

### tokenIdとして用いる値について

トークンをmintする際にはtokenIdを決める必要があります。以前、ERC-721トークンを作った時には0からの連番をトークンIDにしました。

[ERC\-721トークンをOpenZeppelinで作ってみる \- Qiita](https://qiita.com/shora_kujira16/items/6efd834e6a3a3eb8a98c)

この方法でも良いのですが、メタデータを管理する都合上、同じtokenURIが設定されているトークンが存在すると、いろいろと面倒なのではないかと思うようになりました。また、トークンからメタデータはtokenURIを通してリンクが張られているのに対し、メタデータからトークンへリンクが張られていないのも気になりました。

このような理由から、tokenIdとしてはtokenURIのハッシュ値を用いるのが良いのではないかと考えています。ハッシュ化が必要な理由は、tokenIDはuint256である必要があるからです。mint時にtokenURIが必要になってしまいますが、これは前述の「トークンのmintとメタデータの書き込みの順番について」で説明した要件では問題になりません。

ERC-721では（少なくともOpenZeppelinの実装では）すでに発行されたトークンのtokenIdと同じtokenIdを持つトークンは発行できないようになっているので、tokenURIのハッシュ値を用いれば同じtokenURIが設定されているトークンは存在しないことになります。また、tokenURIのハッシュ値を計算すればメタデータからトークンを見つけることも出来ます。

### メタデータと画像データの書き込みの順番について

メタデータに画像を含める場合、画像データのURLをメタデータにのせる必要があります。

Firebaseに画像データと画像以外のデータを同時にアップロードしなければならない場合、アップロードの順番を考慮する必要があります。これは、画像データはFirebase Cloud Storageを用いてアップロードするのに対し、画像以外のデータは Firebase Realtime Database or Firestore or Firebase Cloud Functions を使って書き込むことになり、同時にアップロードすることが出来ないからです（[同時にアップロードする方法もあるのですが、おすすめできないです](https://qiita.com/shora_kujira16/items/95216245ecf06c4cd16d#cloud-functions-%E3%81%A7-multipartform-data-%E3%81%AE%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%82%92%E6%89%B1%E3%81%86)）。

ここでは「メタデータ → 画像」の順番なのか、「画像 → メタデータ」の順番なのか決めたいです。今回は、Cloud Storageにゴミデータが溜まるかどうかという観点で、「メタデータ → 画像」の順番を選びました。

「画像 → メタデータ」の順番では、画像のアップロード後に画像のURLを指定してメタデータを作成することになります。この方法では、画像のアップロードとメタデータの書き込みの間で回線が切断されてしまうと、メタデータに紐付けられていない画像ファイルが残ってしまいます。そのようなゴミデータが残っていないかどうか定期的に巡回する方法も考えられますが、Firebaseの標準機能ではcronは提供されていないので、何かと面倒です。

「メタデータ → 画像」の順番では、メタデータを書きこみ後に画像をアップロードし、画像アップロード後にメタデータと紐付けることになります。画像アップロード後のメタデータとの紐付けのタイミングは、Cloud FunctionsでCloud StorageのonFinalizeイベントハンドラーが呼ばれたタイミングです（[最近Cloud Functionsのイベントハンドラーの名称が変わりました](https://firebase.google.com/docs/functions/beta-v1-diff?hl=ja#storage)）。アップロードされた画像ファイルから、どのようにメタデータを探すのか、ということについてはいろいろなやり方がありますが、今回はメタデータの書き込み時に画像ファイルのファイル名を指定する方法を使いました。

懸念点として、画像データが紐付けられていないメタデータが作成されてしまう恐れがあることが挙げられます。今回は、画像データのアップロードに失敗したらトークンのmintには進まないという方法で対処しました。

### 参考資料

* [WebAPI でファイルをアップロードする方法アレコレ \- Qiita](https://qiita.com/mserizawa/items/7f1b9e5077fd3a9d336b)