[ERC6464] NFTを複数アドレスにapproveする仕組みを理解しよう！

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、**ERC721**形式のNFTを複数のアドレスに`approve()`する仕組みを提案している**ERC6464**についてまとめていきます！

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

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

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

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

# 概要

**ERC721**では、1つのNFTに対して複数のオペレーター（代理人）を承認する仕組みが想定されていませんでした。
そのため、ほとんどのケースで`setApprovalForAll()`が使われてきました。
しかし、これは指定したオペレーターに全てのNFTの管理権限を与えてしまうため、過剰な権限を付与することになりフィッシング詐欺の標的になっています。

**ERC6464**では、特定のNFTごとに個別に複数のオペレーターを承認できる仕組みを提案しています。
これにより、不要なアクセス権を削減し、セキュリティリスクを最小限に抑えることができます。
また、承認を簡単に取り消せる仕組みも提供します。

# 動機

**ERC721**では、NFTの所有者が他のアドレスに操作権限を付与する方法として、以下の2つが定義されています。

- `approve(address,uint256)`
    - 特定の`tokenId`を1オペレーターに操作権限を付与する。
- `setApprovalForAll(address,bool)`
    - 指定したオペレーターに保有する全てののNFTの操作権限を付与する。

複数のNFTマーケットプレイスが登場したことで、NFTを売却する時にそれぞれのマーケットプレイスに特定のNFTのみの`transfer`権限を与えたいケースが増えました。
しかし、現状ではこれを実現する方法がなく、`setApprovalForAll()`を使うしかありません。

これは、最小権限の原則に反し、悪意のある署名を狙った詐欺に悪用されるリスクを生んでいます。
この問題を解決するため、今回のEIPでは、特定のNFTごとに複数のオペレーターを承認できる仕組みを導入します。

## 目的

**ERC6464**の主な目的は以下です。

- マーケットプレイスでの導入が簡単
    - 既存のワークフローを大きく変更する必要がない。
- オフチェーンでの承認状況の管理がしやすい
    - 承認状況を追跡するサービスにも適用しやすい。
- 承認の取り消しが簡単
    - 1つずつ取り消す必要がなく、一括で取り消せる。

## 目的ではないこと

**ERC6464**では以下の点を考慮しません。

- NFTの保護を強化するための追加のセキュリティ機能
    - **ERC6464**の目的は、オペレーター承認の仕組みを細かくすることのみ。
- **ERC1155**との互換性
    - **ERC1155**にも応用できるが、本EIPはあくまで**ERC721**向け。

# 仕様

**ERC6464**では、特定のNFT（`tokenId`）ごとに複数のオペレーターを承認できる新しいインターフェース`IERC6464`を定義しています。
**ERC721**の拡張として機能し、**ERC165**のインターフェース識別子（ID）はまだ未定ですが、**ERC721**および**ERC165**と互換性を持つように設計されています。  

また、オフチェーンのインデクサー向けの注意点として、オペレーターが承認されているかどうかを判断する時以下の条件を考慮する必要があります。  

- `ERC721.Approval(...)`または`ERC721.ApprovalForAll(…, true)`のイベントが発生していて、それが取り消されていない場合オペレーターは承認されていると見なすべき。  
- `ExplicitApprovalFor(..., false)`が発行されていても、それだけではオペレーターが未承認であるとは限らない。  

```js
/**
 * @notice Extends ERC-721 to include per-token approval for multiple operators.
 * @dev Off-chain indexers of approvals SHOULD assume that an operator is approved if either of `ERC721.Approval(…)` or
 * `ERC721.ApprovalForAll(…, true)` events are witnessed without the corresponding revocation(s), even if an
 * `ExplicitApprovalFor(…, false)` is emitted.
 * @dev TODO: the ERC-165 identifier for this interface is TBD.
 */
interface IERC6464 is ERC721 {
    /**
     * @notice Emitted when approval is explicitly granted or revoked for a token.
     */
    event ExplicitApprovalFor(
        address indexed operator,
        uint256 indexed tokenId,
        bool approved
    );

    /**
     * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are
     * revoked for all tokens.
     * @dev MUST be emitted upon calls to `revokeAllExplicitApprovals()`.
     */
    event AllExplicitApprovalsRevoked(address indexed owner);

    /**
     * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are
     * revoked for the specific token.
     * @param owner MUST be `ownerOf(tokenId)` as per ERC721; in the case of revocation due to transfer, this MUST be
     * the `from` address expected to be emitted in the respective `ERC721.Transfer()` event.
     */
    event AllExplicitApprovalsRevoked(
        address indexed owner,
        uint256 indexed tokenId
    );

    /**
     * @notice Approves the operator to manage the asset on behalf of its owner.
     * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner.
     * @dev Approvals set via this method MUST be revoked upon transfer of the token to a new owner; equivalent to
     * calling `revokeAllExplicitApprovals(tokenId)`, including associated events.
     * @dev MUST emit `ApprovalFor(operator, tokenId, approved)`.
     * @dev MUST NOT have an effect on any standard ERC721 approval setters / getters.
     */
    function setExplicitApproval(
        address operator,
        uint256 tokenId,
        bool approved
    ) external;

    /**
     * @notice Approves the operator to manage the token(s) on behalf of their owner.
     * @dev MUST be equivalent to calling `setExplicitApprovalFor(operator, tokenId, approved)` for each `tokenId` in
     * the array.
     */
    function setExplicitApproval(
        address operator,
        uint256[] memory tokenIds,
        bool approved
    ) external;

    /**
     * @notice Revokes all explicit approvals granted by `msg.sender`.
     * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender)`.
     */
    function revokeAllExplicitApprovals() external;

    /**
     * @notice Revokes all excplicit approvals granted for the specified token.
     * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner.
     * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender, tokenId)`.
     */
    function revokeAllExplicitApprovals(uint256 tokenId) external;

    /**
     * @notice Query whether an address is an approved operator for a token.
     */
    function isExplicitlyApprovedFor(address operator, uint256 tokenId)
        external
        view
        returns (bool);
}

interface IERC6464AnyApproval is ERC721 {
    /**
     * @notice Returns true if any of the following criteria are met:
     * 1. `isExplicitlyApprovedFor(operator, tokenId) == true`; OR
     * 2. `isApprovedForAll(ownerOf(tokenId), operator) == true`; OR
     * 3. `getApproved(tokenId) == operator`.
     * @dev The criteria MUST be extended if other mechanism(s) for approving operators are introduced. The criteria
     * MUST include all approval approaches.
     */
    function isApprovedFor(address operator, uint256 tokenId)
        external
        view
        returns (bool);
}
```

## IERC6464 インターフェース  

### イベント  

- `ExplicitApprovalFor`  

特定のオペレーターに対してNFTの管理を許可または取り消したときに発行されます。  

- `AllExplicitApprovalsRevoked`

`revokeAllExplicitApprovals()`が呼ばれたときに発行され、オーナーが保有する全てのNFTに対する承認が取り消されます。  

- `AllExplicitApprovalsRevoked`（NFT単位）  

`revokeAllExplicitApprovals(tokenId)`が呼ばれたときに発行され、特定のNFTに対する承認が取り消されます。  
`ownerOf(tokenId)`のアドレスが記録される。もしNFTが転送された場合、その転送元のアドレスが記録されます。  

### 関数  

- `setExplicitApproval(address operator, uint256 tokenId, bool approved)`  

`operator`に対して、指定した`tokenId`の管理を許可または取り消します。  
`msg.sender`がそのNFTのオーナーまたは適切な権限を持つオペレーターでなければなりません。  
NFTが別のオーナーに送付された場合に自動的に承認が取り消されます。  

- `setExplicitApproval(address operator, uint256[] memory tokenIds, bool approved)`  

複数の`tokenId`を指定し、一括でオペレーターの承認を設定します。  
`setExplicitApproval()`の複数版。  

- `revokeAllExplicitApprovals()`  

`msg.sender`が所有するすべてのNFTに対するオペレーターの承認を一括で取り消します。  

- `revokeAllExplicitApprovals(uint256 tokenId)`  

`msg.sender`が所有する`tokenId`に対するオペレーターの承認を取り消します。  

- `isExplicitlyApprovedFor(address operator, uint256 tokenId) → bool`  

`operator`が`tokenId`の管理を許可されているかどうかを確認します。  

## IERC6464AnyApproval インターフェース  

このインターフェースは、オペレーターがNFTの管理権限を持っているかどうかを簡単に判定するためのものです。  

### 関数  

- `isApprovedFor(address operator, uint256 tokenId) → bool`  

オペレーター`operator`が`tokenId`を管理できるかを判定します。  
以下のいずれかの条件を満たしていれば、`true`を返します。  

- `isExplicitlyApprovedFor(operator, tokenId) == true`（**ERC6464**で定義された方法で承認されている）。  
- `isApprovedForAll(ownerOf(tokenId), operator) == true`（**ERC721**の`setApprovalForAll()`で承認されている）。  
- `getApproved(tokenId) == operator`（**ERC721**の単体承認で許可されている）。

# 補足

## 明示的な承認とは？

**ERC6464**で導入される新しい承認方法は「明示的な承認」と呼ばれます。
これは、従来の`ERC721.approve()`や`ERC721.setApprovalForAll()`と区別しやすくするためのものです。
しかし、基本的な目的は同じで、「オーナーの代理としてオペレーターがNFTを管理できるようにすること」です。

## IERC6464AnyApprovalを分けた理由

`isApprovedFor()`の判定を**IERC6464**に含めず、**IERC6464AnyApproval**として分けた理由はモジュール化を重視したためです。

- **IERC6464**単体での実装がシンプルになる。
- **IERC6464AnyApproval**を導入することで、異なるEIP（今後追加される承認方法を含む）とも統一した形で承認状況をチェックできます。

## AllExplicitApprovalsRevokedのオーナー情報

`AllExplicitApprovalsRevoked(address,uint256)`イベントにはオーナーアドレスが含まれています。
これは、オフチェーンのインデクサーが、どのオーナーのどのNFTに対する承認が取り消されたのかを正しく追跡できるようにするためです。

## IERC6464AnyApprovalの重要性

NFTマーケットプレイスが増え、承認の仕組みも複雑になってきました。
その結果、各マーケットプレイスは、NFTが売却可能かどうかを確認するために複数の異なる承認メカニズムをチェックしなければならなくなっています。

IERC6464AnyApprovalを導入することで、マーケットプレイスは1つの関数を呼び出すだけで承認状況を確認できるようになります。
また、将来的に新しい承認方式が追加されても、共通のインターフェースで対応可能です。
これは、NFTエコシステム全体のデータ処理を簡単にすることにつながります。

# 互換性

## 最小限の変更でERC-721と互換性を維持

**ERC6464**は、元の**ERC721**仕様に対してできる限り小さな変更で済むように設計されています。
そのため、すでに**ERC721**を利用しているプラットフォームとも問題なく互換性があります。

## 既存のNFTプラットフォームでもそのまま利用可能

**ERC6464**をサポートするコントラクトを実装しても、既存のNFTマーケットプレイスやウォレットと互換性を失うことはありません。
`approve()`や`setApprovalForAll()`といった既存の仕組みもそのまま動作します。

# セキュリティ

**ERC6464**では、過剰な権限付与を防ぐための改善が行われていますが、異なる種類の承認方式が混在することによるリスクも考慮する必要があります。

# 引用

Cristian Espinoza (@crisgarner), Simon Fremaux (@dievardump), David Huber (@cxkoda), and Arran Schlosberg (@aschlosberg), "ERC-6464: Multi-operator, per-token ERC-721 approvals. [DRAFT]," Ethereum Improvement Proposals, no. 6464, February 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6464.

# 最後に

今回は「**ERC721**形式のNFTを複数のアドレスに`approve()`する仕組みを提案している**ERC6464**」についてまとめてきました！
いかがだったでしょうか？

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