[ERC5560] NFTを物理的アイテムと引き換え可能かを管理する仕組みを理解しよう！

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、NFTを物理的なアイテムと引き換え可能かを判定する仕組みを提案している**ERC5560**についてまとめていきます！

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

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

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

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

# 概要

**ERC5560**は、**ERC721**形式のNFTに「**引き換え機能（redeem function）**」を追加する仕組みを提案しています。
NFTを使って、物理的なモノと交換できるようにしたい発行者向けの機能です。

# 動機

最近、アーティストや高級アートギャラリー、オークションハウス、ブランドなどが、NFTを通じて物理的な商品を提供するケースが増えています。
例えば、絵画のNFTを持っている人が、その本物の絵を受け取れるようにしたいというようなニーズです。
**ERC5560**では、NFTが物理的なモノと引き換え可能であること（= redeemable）を明確に示せるようにすることでニーズに応えようとしています。

# 仕様

```js:IRedeemableso.sol
import '@openzeppelin/contracts/utils/introspection/ERC165.sol';

/**
 * @dev Implementation of Redeemable for ERC-721s
 *
 */

interface IRedeemable is ERC165 {
	/*
	 * ERC165 bytes to add to interface array - set in parent contract implementing this standard
	 *
	 * bytes4 private constant _INTERFACE_ID_ERC721REDEEM = 0x2f8ca953;
	 */
	 
	/// @dev This event emits when a token is redeemed.
	event Redeem(address indexed from, uint256 indexed tokenId);
	 
	/// @notice Returns the redeem status of a token
	/// @param tokenId Identifier of the token.
	function isRedeemable(uint256 _tokenId) external view returns (bool);

	/// @notice Redeeem a token
	/// @param tokenId Identifier of the token to redeeem
	function redeem(uint256 _tokenId) external;
}
```

**ERC721**に準拠したNFTコントラクトは、この拡張仕様（Redeemable NFT）を任意で実装できます。
実装することで、NFTが「**引き換え可能かどうか**」を判定したり、引き換え処理を実行できるようになります。

## 誰がredeemできるかは発行者が決める

NFTの発行者は、「**誰がredeem（引き換え）できるか**」を自分で決める必要があります。
`redeem`関数を誰でも実行できるようにするのではなく、アクセス制御をしっかりと設定することが重要です。

## 引き換え可能かを確認する方法

誰でも`isRedeemable`という関数を呼び出して、特定のNFTが引き換え可能かどうかを確認できます。  
- 引き換え可能な場合は `true` を返します。  
- すでに引き換えられている場合は `false` になります。

## 外部サービスとの連携

**ERC5560**に対応した外部サービス（マーケットプレイスなど）は、「`Redeem`イベント」を使って、NFTの引き換え状態が変わったタイミングを検知することができます。
これにより、引き換え済みのNFTをマーケットで売買させないなどの対応が可能になります。

## 関数

**ERC5560**に準拠する場合は、以下の関数やイベントを実装する必要があります。

- `isRedeemable(uint256 _tokenId)`  

指定されたNFTが引き換え可能かどうかを返す関数。

- `redeem(uint256 _tokenId)`  

NFTを引き換える処理を実行する関数。

- `event Redeem(address indexed from, uint256 indexed tokenId)`  

引き換えが行われたときに発行されるイベント。

## ERC165

`supportsInterface`という関数を使って、コントラクトが**ERC5560**（`IRedeemable`）に対応しているかどうかを判定できます。  
このとき、引数として `0x2f8ca953` を渡すと `true` を返すようにする必要があります。
これは`IRedeemable`の識別子となる値です。

:::note info
**ERC165**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/541184977a5cba51b759

# 補足

## デフォルトの挙動について

NFTコントラクトをデプロイした直後は、`isRedeemable()` 関数が常に `true` を返す状態になっています。
つまり、その時点ではすべてのNFTが「**引き換え可能**」として扱われます。

また、`redeem()` 関数は特に制限をつけなければ `public`（公開）になっており、誰でも実行できてしまいます。
これはセキュリティ上よくないので、引き換え処理を許可する相手を制限することが強く推奨されています。

## アクセス制御の推奨方法

誰でも勝手に `redeem()` を実行できると、本来のNFT保有者以外が引き換えてしまう危険があります。
そこで、以下のような `require` を使って、実行者がNFTの保有者であることをチェックすることが一般的です。

```js
require(ownerOf(tokenId) == msg.sender, "ERC721Redeemable: You are not the owner of this token");
```

## 引き換え後の状態

`redeem()` 関数が正常に実行された後、そのトークンに対して `isRedeemable()` を呼び出すと `false` が返るようになります。
これで「**既に引き換え済み**」という状態がわかります。

## Redeemイベント

`redeem()` が実行されたタイミングでは、必ず以下のイベントを発行する必要があります。

```js
event Redeem(address indexed from, uint256 indexed tokenId);
```

これにより、外部のサービスやDAppが「**誰がどのNFTを引き換えたのか**」をリアルタイムで追跡できるようになります。
例えば、NFTのマーケットプレイス側で「**このNFTはもう使われたから販売リストから外す**」といった処理を実行できます。

# 互換性

**ERC5560**は、**ERC721**と完全に互換性があります。

# 参考実装

```js:ERC721Redeemable.sol
contract ERC721Redeemable is ERC721, Redeemable {

	constructor(string memory name, string memory symbol) ERC721(name, symbol) {
	}

	function isRedeemable(uint256 tokenId) public view virtual override returns (bool) {
		require(_exists(tokenId), "ERC721Redeemable: Redeem query for nonexistent token");
		return super.isRedeemable(tokenId);
	}

	function redeem(uint256 tokenId) public virtual override {
		require(_exists(tokenId), "ERC721Redeemable: Redeem query for nonexistent token");
		require(ownerOf(tokenId) == msg.sender, "ERC721Redeemable: You are not the owner of this token");
		super.redeem(tokenId);
	}

	function supportsInterface(bytes4 interfaceId) public view override(ERC721, Redeemable) returns (bool) {
		return super.supportsInterface(interfaceId);
	}
}
```

# 引用

Olivier Fernandez (@fernandezOli), Frédéric Le Coidic (@FredLC29), Julien Béranger (@julienbrg), "ERC-5560: Redeemable NFTs [DRAFT]," Ethereum Improvement Proposals, no. 5560, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5560.

# 最後に

今回は「NFTを物理的なアイテムと引き換え可能かを判定する仕組みを提案している**ERC5560**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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