[ERC3569] NFTのメタデータが変更されないことを保証する仕組みを理解しよう！

# はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、NFTのメタデータが変更されないことを保証する仕組みを提案している**ERC3569**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

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

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

Sealed NFT Metadata Extension（シールドNFTメタデータ拡張）は、NFTのメタデータを変更不可能な状態で固定する提案です。
この仕組みにより、作成者はNFTのメタデータを一度だけ確定させることができ、それ以降は変更できないことがスマートコントラクト上で保証されます。

この拡張は以下のような特徴を持ちます。

- メタデータが変更されないことを確認できる
- 複数のNFTのメタデータを一度にまとめて固定できる
- 複数のNFTのメタデータを1つのファイルから読み込んだりキャッシュできる

具体的には、作成者は `seal` 関数を呼び出すことで、連続する複数のNFTに対して一括でメタデータを固定できます。
引数として渡されるURIはIPFSなどの分散型ストレージサービスを指し、このURIがスマートコントラクトに記録されます。
URIの先には、トークンIDごとにメタデータを記載したJSONが存在します。
各トークンは一度しかシールできず、シールされたトークンについては後から確認することが可能です。

この機能はNFTの作成時でも作成後でも実行可能で、柔軟な運用が可能です。

# 動機

元々の**ERC721**および**ERC1155**のメタデータ拡張では、`tokenURI`関数を通じて単一のNFTに対するメタデータURIを返す設計になっています。
しかし、このURIは中央集権的なサーバー上にホスティングされていることが多く、いつでも内容が書き換えられるリスクがあります。
NFTプロジェクトにおいてこれはrug pullにつながる可能性があります。

さらに、多数のNFTのメタデータを変更する場合、O(n)の操作が必要になり、ガスコストが非常に高くなる問題があります。

**ERC3569**では、分散型ストレージに格納された1つのJSONファイルから複数のNFTメタデータを一括で参照できるようにすることで、以下の課題を解決します。

- メタデータの不変性の保証（ユーザーが信頼できる）
- NFT作成者が必要に応じて後からシールできる柔軟性の提供
- デジタルアートやマーケティングプロジェクトのような場面で、初期のミス修正やダイナミックNFTへの対応が可能

このように、Sealed NFT Metadata Extensionは、柔軟性と信頼性の両立を目的とした現実的かつセキュアな解決策を提供しています。
特に将来的に不変性が求められる場面での利用が想定され、NFTの真正性を重視するプラットフォームやユーザーにとって有益です。

# 仕様

## インターフェース

```js:SealedMetadata.sol
interface SealedMetadata {
  /**
    @notice This function is used to set a sealed URI for the given range of tokens.
    @dev
      - If the sealed URI is being set for one token then the fromTokenId and toTokenId
      values MUST be the same.

      - If any token within the range of tokens specified has already
      been sealed then this function MUST throw.

      - This function MAY be called at the time of NFT creation, or after the NFTs have been created.

      - It is RECOMMENDED that this function only be executable by either the creator of the smart contract,
        or the creator of the NFTs, but this is OPTIONAL and should be implemented based on use case.

      - This function MUST emit the Sealed event

      - The URI argument SHOULD point to a JSON file hosted within a decentralized file system like IPFS

    @param fromTokenId The first token in a consecutive range of tokens
    @param toTokenId The ending token in a consecutive range of tokens
    @param uri A URI which points to a JSON file hosted on a decentralized file system.
  */
  function seal(uint256 fromTokenId, uint256 toTokenId, string memory uri) external;

  /**
    @notice This function returns the URI which the sealed metadata can be found for the given token ID
    @dev
      - This function MUST throw if the token ID does not exist, or is not sealed

    @param tokenId Token ID to retrieve the sealed URI for

    @return The sealed URI in which the metadata for the given token ID can be found
  */
  function sealedURI(uint256 tokenId) external view returns (string);

  /**
    @notice This function returns a boolean stating if the token ID is sealed or not
    @dev This function should throw if the token ID does not exist

    @param tokenId The token ID that will be checked if sealed or not

    @return Boolean stating if token ID is sealed
  */
  function isSealed(uint256 tokenId) external view returns (bool)

  /// @dev This emits when a range of tokens is sealed
  event Sealed(uint256 indexed fromTokenId, uint256 indexed toTokenId, string memory uri);

}
```

### `seal`

```js
function seal(uint256 fromTokenId, uint256 toTokenId, string memory uri) external;
```

NFTの範囲に対してシールされたメタデータURIを設定する関数。
この関数は、指定された連続範囲（`fromTokenId` から `toTokenId`）のNFTに対して一括でURIを登録し、メタデータを固定します。
すでにシール済みのトークンIDが範囲内に含まれている場合はエラーを返します。
呼び出しタイミングはNFTの作成時または作成後のいずれでも可能です。
セキュリティ上の観点から、呼び出し権限はコントラクトの作成者やNFT作成者に限定することが推奨されます。
関数実行時には `Sealed` イベントが発行されます。

**引数**

* `fromTokenId`
    * シール対象の最初のトークンID。
* `toTokenId`
    * シール対象の最後のトークンID。
* `uri`
    * 分散型ストレージ（例：IPFS）上のメタデータJSONファイルのURI。

### `sealedURI`

```js
function sealedURI(uint256 tokenId) external view returns (string);
```

指定したトークンIDに対応するシール済みメタデータのURIを返す関数。
この関数は、指定したトークンIDがシール済みである場合そのURIを返します。
存在しないトークンIDや未シールのトークンIDに対してはエラーを返す必要があります。

**引数**

* `tokenId`
    * URIを取得したいNFTのトークンID。

**戻り値**

* `string`
    * 指定されたNFTに対して設定されたシール済みメタデータのURI。

### `isSealed`

```js
function isSealed(uint256 tokenId) external view returns (bool);
```

トークンIDがシールされているかを確認する関数。
この関数は、特定のNFTが `seal` 関数によってシール済みであるかを判定します。
存在しないトークンIDに対しては例外を投げるべきです。

**引数**

* `tokenId`
    * チェック対象となるNFTのトークンID。

**戻り値**

* `bool`
    * トークンIDがシール済みであれば `true`、そうでなければ `false`。

### `Sealed`

```js
event Sealed(uint256 indexed fromTokenId, uint256 indexed toTokenId, string memory uri);
```

トークンの範囲に対してメタデータがシールされた時に発行されるイベント。
`seal` 関数の呼び出しによって発行されるイベントで、どの範囲のトークンがどのURIでシールされたかを示します。
プラットフォームやブロックチェーンエクスプローラーがこのイベントを検知することで、該当NFTのメタデータを自動でキャッシュすることが可能になります。

**パラメータ**

* `fromTokenId`
    * シール対象の開始トークンID。
* `toTokenId`
    * シール対象の終了トークンID。
* `uri`
    * メタデータが格納された分散型ストレージのURI。

## ERC721Metadata

```typescript
type ERC721Metadata = {
  name?: string;
  image?: string;
  description?: string;
}
```

**ERC721** トークンの基本的なメタデータ構造を定義する型。
トークン名、画像URL、説明文など、NFT表示に必要な基本情報を持つ構造体です。
全てのフィールドは任意（optional）であり、用途に応じて部分的に使用することが可能です。

**パラメータ**

* `name`
    * NFTの名称。
* `image`
    * 表示用画像のURL。
* `description`
    * NFTに関する説明文。

### SealedMetaDataJson

```typescript
type SealedMetaDataJson = {
  [tokenId: string]: string | ERC721Metadata;
}
```

シールされたNFTのメタデータを一括で格納するJSON構造を定義。
この型は、複数のNFTに対応するメタデータを1つのJSONファイルにまとめることを目的としています。
キーはトークンID（文字列）、値は直接メタデータオブジェクトまたは別のURI（例：IPFS）です。
これにより、ガスコストを削減しつつスケーラブルなメタデータ提供が可能になります。

**パラメータ**

* `tokenId`
    * メタデータが紐づくNFTのトークンID（文字列形式）。
* `string | ERC721Metadata`
    * メタデータオブジェクト本体、または外部URIへの参照。

# 補足

## 分散型ストレージを強制しない理由

**ERC3569**では、シールされたURIがIPFSなどの分散型ファイルストレージにホストされていることをスマートコントラクト側で検証しない設計になっています。
その理由は、**ERC3569**を将来的にも柔軟に利用できるようにするためです。

代わりに、URIの検証はクライアント側で行うことが想定されています。
これにより、**ERC3569**自体はどのようなURIスキームにも対応できるようになり、IPFS以外の新しい技術への適応も可能となります。

## 1つのJSONファイルに複数のメタデータを含める理由

複数のNFTのメタデータを1つのJSONファイルにまとめて含めることが推奨されています。
これにより、トークンごとに個別のトランザクションでメタデータを設定する必要がなくなり、ガスコストを大幅に削減できます。

また、1つのファイルで完結することで、プラットフォームやブロックチェーンエクスプローラーはそのファイルをキャッシュ（保存）しておくことで、効率的に表示や取得ができるようになります。
ファイルが変更されることがない前提のため、読み込みや検証処理も高速になります。

## Sealedイベントを発行する理由

`Sealed` イベントは、NFTがシールされたタイミングを他のアプリケーションやプラットフォームが検出する手段として用いられます。
このイベントが発行されることでNFTの状態変化を検知し、キャッシュの更新やメタデータの再取得が自動的に行われるようになります。

そのため、イベントの発行は単なる記録ではなく、データ整合性とユーザビリティの両面で重要な役割を果たします。

## JSONファイル内の値としてURIを許容する理由

NFTのメタデータが非常に大きい場合や大量のトークンを扱う場合に、全ての情報を1つのJSONファイルに直接含めるとファイルサイズが肥大化してしまう恐れがあります。

このような状況に対応するため、トークンIDの値として直接メタデータオブジェクトではなく、別のURIを指す文字列を使うことも許可されています。
これにより、メインのJSONファイルは軽量化され、メタデータの構造をより柔軟に設計することができます。
これはスケーラビリティの観点でも非常に重要です。

# 引用

Sean Papanikolas (@pizzarob), "ERC-3569: Sealed NFT Metadata Standard [DRAFT]," Ethereum Improvement Proposals, no. 3569, May 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3569.

# 最後に

今回は「NFTのメタデータが変更されないことを保証する仕組みを提案している**ERC3569**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4