[ERC6786] ロイヤリティを集計して受け取ることができる仕組みを理解しよう！

# はじめに

初めまして。
**CryptoGames**というブロックチェーンゲーム企業でエンジニアをしている **cardene（かるでね）** です！
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

https://cryptogames.co.jp/

代表的なゲームは**クリプトスペルズ**というブロックチェーンゲームです。

https://cryptospells.jp/

今回は、**ERC2981**によってクリエイターが受け取ったロイヤルティを計算して、取得できる仕組みを提案している規格である**ERC6786**についてまとめていきます！

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

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

:::note warn
**6786**は現在（2023年10月18日）では「**Draft**」段階です。
:::

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

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

# 概要

この規格により、誰でも特定のNFTに対してロイヤルティを支払うことができ、また支払ったロイヤルティの金額を記録することができます。
この規格を通じて支払いが実行されるたびにその値が累積され、情報が公開されるようになります。

# 動機

この標準は、多くのNFT取引プラットフォームがNFTの販売や再販時に作成者にロイヤルティを支払うしくみを提供していないか、支払いが不透明である状況で、NFTのクリエイターに対するインセンティブを確立する必要がある背景から生まれました。

このコントラクトは、特定のNFTについて、クリエイターが受け取ったロイヤルティの金額を計算する方法を提供します。
これは、NFTのロイヤルティ情報を整理し、異なるNFTカテゴリに分類する時に役立ちます。
また、「**デビット**」の概念は、ロイヤルティをサポートしていないマーケットプレイスで行われたNFTの取引において、未払いのロイヤルティが存在するかどうかを確認し、その場合に支払い方法を提供することを目指しています。

多くのNFT取引プラットフォームが、ロイヤルティ支払いを簡素化したり、中央集権的なアプローチを取っている一方で、誰でもNFTのクリエイターに対してロイヤルティを支払う手段を提供することを望んでいます。
これは、NFTクリエイターの作品へのサポート手段として機能します。

この標準は、NFTエコシステム内でクリエイターのインセンティブを強化し、ロイヤルティ支払いの透明性と効率性を提供します。

# 仕様

**ERC6786**に準拠するすべてのコントラクトは、以下のように定義されたインタフェースを実装する必要があります。

## コントラクトのインターフェース

```javascript
// @title Royalty Debt Registry
/// Note: the ERC-165 identifier for this interface is 0x253b27b0

interface IERC6786 {

    // Logged when royalties were paid for a NFT
    /// @notice Emitted when royalties are paid for the NFT with address tokenAddress and id tokenId
    event RoyaltiesPaid(address indexed tokenAddress, uint256 indexed tokenId, uint256 amount);

    /// @notice sends msg.value to the creator of a NFT
    /// @dev Reverts if there are no on-chain informations about the creator
    /// @param tokenAddress The address of NFT contract
    /// @param tokenId The NFT id
    function payRoyalties(address tokenAddress, uint256 tokenId) external payable;

    /// @notice Get the amount of royalties which was paid for a NFT
    /// @dev 
    /// @param tokenAddress The address of NFT contract
    /// @param tokenId The NFT id
    /// @return The amount of royalties paid for the NFT
    function getPaidRoyalties(address tokenAddress, uint256 tokenId) external view returns (uint256);
}
```

これらの規定は、コントラクトの設計と実装に関するルールを説明しています。
以下はそれぞれの規定の説明です。

- すべての`view`と宣言された関数は、`pure`または`view`として実装できます。
   - コントラクト内の関数が外部の状態を変更せず、読み取り専用の操作を行う場合、その関数は「`pure`」または「`view`」として実装できます。
   - これにより、関数がコントラクトの状態を変更しないことが明確に示されます。
- `payRoyalties`関数は`public`または`external`として実装できます。
   - `payRoyalties`関数は、ロイヤルティを支払うための関数で、外部から呼び出すことができる場合、「`public`」または「`external`」として実装できます。
   - これにより、外部のエンティティがこの関数を利用できるようになります。
- `payRoyalties`関数が呼び出された際には、必ず`RoyaltiesPaid`というイベントを発行しなければなりません。
   - `payRoyalties`関数が呼び出されるたびに、ロイヤルティが支払われたことを示すために「`RoyaltiesPaid`」というイベントを発行する必要があります。
   - このイベントは、トランザクションが正常に完了したことを他のアクターに通知するのに役立ちます。

- `supportsInterface`関数は、`0x253b27b0`という引数で呼び出された場合、必ず`true`を返さなければなりません。
   - `supportsInterface`関数は、指定された引数（`0x253b27b0`）に対して必ず「`true`」を返す必要があります。
   - この関数は、コントラクトが特定の機能やインターフェースをサポートしているかどうかを確認するために使用されます。

これらの規定に従うことにより、コントラクトの設計と実装が透明性を持ち、他のコントラクトとの連携が円滑に行えるようになります。

# 補足

支払いは、そのNFTがネイティブトークンで行えるため、支払われたロイヤルティの金額を簡単に追跡できます。
この情報を公開し、誰でもクリエイターがロイヤルティを受け取ったかどうかを確認できるようにしたいと考えています。
これは、アンダーザテーブルの取引やロイヤルティをサポートしないマーケットプレイスの場合に特に有用です。

支払いに使用される関数は、NFTの所有者だけでなく、誰も（一般のユーザーも含む）がクリエイターをサポートするためにいつでも呼び出すことができます。
また、どんなトークンでも支払われたロイヤルティの金額を確認できる方法も提供されており、これは誰でも利用できます。

クリエイターのオンチェーンデータを取得するためには、**ERC2981**を使用することができますが、他のオンチェーンメソッドを使用してクリエイターアドレスを取得することもできます。

:::note info
**ERC2981**については以下を参考にしてください。
:::

https://qiita.com/cardene/items/456ccacca080fd72e3ec

:::note info
このように、支払いはネイティブのトークンで行われ、ロイヤルティ情報は透明に公開されます。
また、支払い関数は誰でも利用可能で、クリエイターをサポートする手段として活用できます。
さらに、**ERC2981**などのオンチェーンメソッドを使用して、クリエイターアドレスを取得することができます。
:::

# 後方互換性

このERCは後方互換性を導入するものではないです。

# テスト

以下にテストコードが書かれています。

https://eips.ethereum.org/assets/eip-6786/test/ERC6786.test.js

# 参考実装

```javascript
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "./IERC6786.sol";
import "./utils/IERC2981.sol";

contract ERC6786 is IERC6786, ERC165 {

    //Mapping from token (address and id) to the amount of paid royalties
    mapping(address => mapping(uint256 => uint256)) private _paidRoyalties;

    /*
     *     bytes4(keccak256('payRoyalties(address,uint256)')) == 0xf511f0e9
     *     bytes4(keccak256('getPaidRoyalties(address,uint256)')) == 0xd02ad759
     *
     *     => 0xf511f0e9 ^ 0xd02ad759 == 0x253b27b0
     */
    bytes4 private constant _INTERFACE_ID_ERC6786 = 0x253b27b0;

    /*
     * bytes4(keccak256('royaltyInfo(uint256,uint256)')) == 0x2a55205a
     */
    bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;

    /// @notice This is thrown when there is no creator related information
    /// @param tokenAddress -> the address of the contract
    /// @param tokenId -> the id of the NFT
    error CreatorError(address tokenAddress, uint256 tokenId);

    /// @notice This is thrown when the payment fails
    /// @param creator -> the address of the creator
    /// @param amount -> the amount to pay
    error PaymentError(address creator, uint256 amount);

    function checkRoyalties(address _contract) internal view returns (bool) {
        (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_ERC2981);
        return success;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
        interfaceId == type(IERC6786).interfaceId ||
        super.supportsInterface(interfaceId);
    }

    function payRoyalties(address tokenAddress, uint256 tokenId) external override payable {
        if (!checkRoyalties(tokenAddress)) {
            revert CreatorError(tokenAddress, tokenId);
        }
        (address creator,) = IERC2981(tokenAddress).royaltyInfo(tokenId, 0);
        (bool success,) = payable(creator).call{value : msg.value}("");
        if(!success) {
            revert PaymentError(creator, msg.value);
        }
        _paidRoyalties[tokenAddress][tokenId] += msg.value;

        emit RoyaltiesPaid(tokenAddress, tokenId, msg.value);
    }

    function getPaidRoyalties(address tokenAddress, uint256 tokenId) external view override returns (uint256) {
        return _paidRoyalties[tokenAddress][tokenId];
    }
}
```

### `_paidRoyalties`

```javascript
// Mapping from token (address and id) to the amount of paid royalties
mapping(address => mapping(uint256 => uint256)) private _paidRoyalties;
```

**概要**
トークン（アドレスとID）から支払われたロイヤルティの金額を管理する配列。

**詳細**
- 特定のNFTトークンに支払われたロイヤルティの金額を記録する配列です。
- このマップは、NFTのアドレスとIDに基づいて、支払われたロイヤルティの金額を追跡します。
- `_paidRoyalties`はプライベート変数として宣言されており、コントラクト内からのみアクセス可能です。

**パラメータ**
- `address`
    - NFTのアドレス。
- `uint256`
    - NFTのID。
- `uint256`
    - 支払われたロイヤルティの金額。

---

### `_INTERFACE_ID_ERC6786`

```javascript
/*
 * bytes4(keccak256('payRoyalties(address,uint256)')) == 0xf511f0e9
 * bytes4(keccak256('getPaidRoyalties(address,uint256)')) == 0xd02ad759
 *
 * => 0xf511f0e9 ^ 0xd02ad759 == 0x253b27b0
 */
bytes4 private constant _INTERFACE_ID_ERC6786 = 0x253b27b0;
```

**概要**
**ERC6786**インターフェースの識別子を表す変数。

**詳細**
- `_INTERFACE_ID_ERC6786`は、**ERC6786**インターフェースで使用される関数の識別子を生成します。
- この識別子は、`payRoyalties`および`getPaidRoyalties`関数を識別し、インターフェースのサポートを示すのに使用されます。

---

### `_INTERFACE_ID_ERC2981`

```javascript
/*
 * bytes4(keccak256('royaltyInfo(uint256,uint256)')) == 0x2a55205a
 */
bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;
```

**概要**
**ERC2981**インターフェースの識別子を表す変数。

**詳細**
- `_INTERFACE_ID_ERC2981`は、**ERC2981**インターフェースで使用される関数の識別子を生成します。
- この識別子は、`royaltyInfo`関数を識別し、トークンのロイヤルティ情報を取得するのに使用されます。

---

### `CreatorError`

```javascript
/// @notice This is thrown when there is no creator related information
/// @param tokenAddress -> the address of the contract
/// @param tokenId -> the id of the NFT
error CreatorError(address tokenAddress, uint256 tokenId);
```

**概要**
クリエイターに関連する情報が存在しない場合に発生するエラー。

**詳細**
- `CreatorError`は、クリエイターに関する情報が不足している場合に投げられるエラーです。
- エラーメッセージには、トークンのアドレスとIDが含まれます。

**パラメータ**
- `tokenAddress`
    - コントラクトのアドレス。
- `tokenId`
    - NFTのID。

---

### `PaymentError`

```javascript
/// @notice This is thrown when the payment fails
/// @param creator -> the address of the creator
/// @param amount -> the amount to pay
error PaymentError(address creator, uint256 amount);
```

**概要**
支払いが失敗した場合に発生するエラー。

**詳細**
- `PaymentError`は、支払いが正常に行われなかった場合に長られるエラーです。
- エラーメッセージには、クリエイターのアドレスと支払い金額が含まれます。

**パラメータ**
- `creator`
    - クリエイターのアドレス。
- `amount`
    - 支払い金額。

---

### `checkRoyalties`

```javascript
function checkRoyalties(address _contract) internal view returns (bool) {
    (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_ERC2981);
    return success;
}
```

**概要**
指定されたコントラクトが**ERC2981**をサポートしているか確認する関数。

**詳細**
- 指定されたアドレスのコントラクトが**ERC2981**をサポートしているかどうかを調べるための内部関数です。
- `IERC165(_contract).supportsInterface(_INTERFACE_ID_ERC2981)`は、指定されたコントラクトが**ERC2981**をサポートしている場合に`true`を返します。
- 関数の戻り値は、**ERC2981**サポートの有無を示すブール値です。

**引数**
- `_contract`
    - チェックするコントラクトのアドレス

**戻り値**
- `bool`
    - コントラクトが**ERC2981**をサポートしている場合は`true`、それ以外は`false`

---

### `supportsInterface`

```javascript
/**
 * @dev See {IERC165-supportsInterface}.
 */
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
    return
    interfaceId == type(IERC6786).interfaceId ||
    super.supportsInterface(interfaceId);
}
```

**概要**
**ERC6786**および親クラスのサポートインターフェースを処理する関数。

**詳細**
- `supportsInterface`関数は、**ERC6786**および親クラスのサポートインターフェースを処理し、要求されたインターフェースがサポートされているかどうかを返します。
- **ERC6786**のインターフェースIDと親クラスのサポートインターフェースを確認し、該当する場合は`true`を返します。

**引数**
- `interfaceId`
    - チェックするインターフェースのID。

**戻り値**
- `bool`
    - 要求されたインターフェースがサポートされている場合は`true`、それ以外は`false`。

---

### `payRoyalties`

```javascript
function payRoyalties(address tokenAddress, uint256 tokenId) external override payable {
    if (!checkRoyalties(tokenAddress)) {
        revert CreatorError(tokenAddress, tokenId);
    }
    (address creator,) = IERC2981(tokenAddress).royaltyInfo(tokenId, 0);
    (bool success,) = payable(creator).call{value : msg.value}("");
    if(!success) {
        revert PaymentError(creator, msg.value);
    }
    _paidRoyalties[tokenAddress][tokenId] += msg.value;

    emit RoyaltiesPaid(tokenAddress, tokenId, msg.value);
}
```

**概要**
ロイヤルティを支払うためのエントリーポイントであり、指定されたNFTに対してロイヤルティを支払う関数。

**詳細**
- `payRoyalties`関数は、指定されたトークンとトークンIDに対してロイヤルティを支払うための外部関数です。
- 最初に、`checkRoyalties`関数を使用して、指定されたトークンが**ERC2981**をサポートしているかどうかを確認します。
- 次に、指定されたトークンとトークンIDからクリエイターのアドレスを取得します。
- 支払いは`payable(creator).call{value : msg.value}("")`を使用してクリエイターに送金されます。
- 支払いに失敗した場合はエラーが投げられます。
- 成功した支払い後、支払われたロイヤルティの金額を記録し、`RoyaltiesPaid`イベントを発行します。

**引数**
- `tokenAddress`
    - NFTのコントラクトアドレス。
- `tokenId`
    - NFTのID。

---

### `getPaidRoyalties`

```javascript
function getPaidRoyalties(address tokenAddress, uint256 tokenId) external view override returns (uint256) {
    return _paidRoyalties[tokenAddress][tokenId];
}
```

**概要**
指定されたNFTに対して支払われたロイヤルティの金額を取得する関数。

**詳細**
- `getPaidRoyalties`関数は、指定されたトークンアドレスとトークンIDに対して支払われたロイヤルティの金額を取得するための関数です。
- `_paidRoyalties`マップから該当するトークンとトークンIDのエントリを検索し、そのロイヤルティの金額を返します。

**引数**
- `tokenAddress`
    - NFTのコントラクトアドレス。
- `tokenId`
    - NFTのID、

**戻り値**
- `uint256`
    - 支払われたロイヤルティの金額。

---

# セキュリティ考慮事項

この規格の実装に直接関係するセキュリティの考慮事項はありません。

# 引用

Otniel Nicola (@OT-kthd), Bogdan Popa (@BogdanKTHD), "ERC-6786: Registry for royalties payment for NFTs [DRAFT]," Ethereum Improvement Proposals, no. 6786, March 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6786.

# 最後に

今回は「**ERC2981**によってクリエイターが受け取ったロイヤルティを計算して、取得できる仕組みを提案している規格である**ERC6786**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://chaldene.net/

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58


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