[ERC5023] NFTをコピーして送付できる仕組みを理解しよう！

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回の記事では、NFTをコピーして他のアドレスに送付することができる**ERC5023**についてまとめていきます！

:::note info
以下を参考にまとめていきます！
:::

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

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

https://chaldene.net/erc721

# 概要
この提案では、他と交換できない価値を持つトークンを共有可能にするインターフェースを標準化します。
共有可能なトークンは、既存のトークンを別のアドレス用ににコピーして作成されます。
この処理により、「**誰**」が「**何**」を「**誰**」に共有したかを管理することができます。

# 動機
**ERC721**や**ERC1155**などのNFT規格は、交換できないデジタルリソースを標準化するために開発されました。
しかし、多くの交換できない価値を持つデジタルリソースは希少である必要がありません。

私たちは、**anti-rival logic**を示す新しいタイプのインセンティブメカニズムをエコシステムに取り入れることで、外部効果を捉えようと試みています。

:::note info
**anti-rival logic**とは、リソースを共有することで価値が増える仕組みです。
通常、物理的なリソースは限られており、1つの人がそれを使うと他の人が使えなくなるため、競合（リバル）する性質があります。
しかし、デジタルリソースや特定の場合においては、共有することで価値が減ることなく、むしろ増える場合があります。
:::

![*anti-rival.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/9a40b4b5-a3c7-4469-aa93-14b22c30fc5f.png)

:::note info
**インセンティブメカニズム**とは、経済的なシステムにおいて人々の行動や活動を促進するための仕組みです。
具体的には、人々が特定の行動を取ると、その対価として共有可能なトークンが付与されます。
:::

:::note info
ここでいう**外部効果**とは、トークンの共有によって他の参加者やエコシステム全体に対して好ましい影響を与える効果を指します。
通常のトークンは、所有者がそのトークンの利益を獲得することが主な目的ですが、共有可能なトークンは、他の人とそのトークンを共有することで価値が増加する特性を持ちます。つまり、トークンを共有することで、共有者やエコシステム全体にポジティブな影響を及ぼすことが期待されます。
以下のような具体例が挙げられます。

1. ソーシャルメディアプラットフォームの共有可能なトークン
あるコンテンツが共有されることで、そのコンテンツのクリエイターだけでなく、共有した人や閲覧者にもポジティブな効果が生じます。
共有者はコンテンツの人気に寄与することで、他のユーザーに見られやすくなったり、より多くのいいねやコメントを受ける可能性が高まります。
また、閲覧者にとっても面白いコンテンツを見つける機会が増えたり、より多くの多様なコンテンツにアクセスできるようになるなど、良い体験が提供されます。

2. 分散型ファイル共有プラットフォームの共有可能なトークン
あるファイルが共有されることで、ファイルのアップローダーだけでなく、共有した人やダウンロードした人にもメリットがあります。
共有者はファイルの普及に寄与することで、多くの人に価値ある情報を提供する役割を果たします。また、ダウンロードした人は便利な情報やデータを手に入れることで、自分の目的に役立てることができます。
:::

![外部効果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/0f7be6d9-e474-a3df-c21f-47b39e8d1579.png)


これらのインセンティブは会計単位として機能し、共有できる媒体としても機能します。

:::note info
**会計単位**とは、トークンを所有することによって、特定の経済的な行動や参加を表現し、その対価としてトークンが付与されるという仕組みのことです。
トークンを通じて、参加者は特定の行動を取ることでトークンを受け取ることができるため、インセンティブとして機能します。
例えば、特定のコンテンツをシェアしたり、プラットフォーム内で貢献したりすることで、トークンが付与される仕組みがあげられます。
:::

:::note info
**共有できる媒体**とは、デジタルな性質を持つアイテムということです。
トークンはデジタルアセット、デジタルコンテンツ、デジタルアートなどを代表する手段として利用できます。
トークンを持つことで、デジタルなアイテムを所有する権利を得ることができるため、アイテムの表現としての機能を果たします。
また、共有可能なトークンは共有されることで価値が増加する特性を持ちます。
つまり、他の人とこのトークンを共有することで、そのトークンの価値が高まる可能性があります。
:::

共有可能なNFTは、デジタルの財産を共有することができる柔軟なトークンであり、トランザクションコストが`0`で共有可能です。
これによって経済システムにおける正の外部効果（他の人やエコシステムに対して好ましい影響を及ぼす効果）を測定・評価するために利用され、参加者やエコシステム全体の価値向上を促進する役割を果たすことが期待されています。


**ERC721**や**ERC1155**などの通常のNFT標準は、共有の方法や仕組みを定義していません。
代わりに、ERC標準はトークンの`mint`（発行）やトークンのトランザクションなどの一般的な競合するユースケースのためのインターフェースを定義しています。
一方、共有可能なトークンは、通常のNFTとは異なり、複数のアドレスによって共有することができるように設計されています。
この共有可能性の要件は、既存のトークンを別のアドレスに対して再度`mint`（発行）してコピーできることを意味します。
これにより、元のトークンのバージョンは保持されたまま、別のアドレスにも同じトークンのコピーが与えられます。

:::note info
**要約**
共有可能なトークンは、通常のNFT標準との互換性を保ちつつ、複数のアドレスによる共有を可能にする特殊な機能を提供する新しいタイプのトークンです。
この共有可能性は、既存のトークンを別のアドレスにコピーして共有することで実現され、元のバージョンも保持されるため、1つのトークンが複数の当事者によって価値を持つことができます。
:::

![share.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/08f7e59c-ea22-a70f-9fd8-f5a33839518c.png)


共有は非常に興味深い概念であり、異なる視点や理解が可能です。
例えば、共有について話す際には、デジタルのコピーを行うと考えることができます。
これは、あるデジタルリソース（例：写真、音楽、動画など）を他の人に提供することで、同時に自分自身がそのリソースの所有権を保持することを意味します。
このような共有の形態は、デジタルなリソースの複製として捉えることができます。

共有可能なトークンは、異なる形態で共有が行われることが可能です。
1つは、デジタルのアセットそのものを複数の人で共有する形式であり、もう1つは特定のデジタルコンテンツの使用権を複数の人に提供する形式です。
このように、共有可能なトークンはさまざまな共有の方法に適応できるように設計されています。

共有可能なトークンは、デジタルなリソースの共有を含む様々な共有の形態を実現することができる新しいタイプのトークンです。
このトークンの柔軟性と実装の自由度により、さまざまな用途や要件に合わせた共有可能なトークンの活用が可能となります。

共有可能なトークンは、スマートコントラクトの実装レベルで非譲渡性（送付不可）に設定することができます。
このような設定により、共有可能な非譲渡性のトークンが作成されます。
これは、トークンを一度`mint`（発行）した後に、他のアドレスによる所有権の譲渡を防ぐことを意味します。

共有可能なNFTのインターフェースを使用することで、共有可能な非譲渡性のNFTを定義することができます。
これは、共有可能性という抽象度の高い概念をより広い範囲の人々が理解できるようにするための取り組みです。

このインターフェース実装では、共有可能性のコンセプトを満たすために必要な最小限の関数を定義しています。
これは、共有可能なトークンがどのように作成され、共有され、非譲渡性が確保されるかを規定するものです。
このような共通のインターフェースを使用することで、共有可能性の概念を理解しやすくし、トークンの共有や非譲渡性の実装を容易にすることができます。

![sbt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570269/e569e070-052c-fb1f-182c-597de3cf4c5e.png)

# 仕様

```javascript
interface IERC5023 is IERC165 {

  event Share(address indexed from, address indexed to, uint256 indexed tokenId, uint256 derivedFromtokenId);

  function share(address to, uint256 tokenIdToBeShared) external returns(uint256 newTokenId);

}
```

## Share

トークンが共有され、新しい所有者に`remint`（再発行）され、それがコントラクトを呼び出したアドレス（`function caller`）ではない別のアドレスに送信されたときに発行されるイベント。

- `from`
    - 元の所有者のアドレス。
- `to`
    - 新しい所有者のアドレス。
- `tokenId`
    - 元のトークンのID。
- `derivedFromtokenId`
    - 新しいトークンのIDの元となったトークンID。

## share

既存のトークンを共有・再発行し、新しいトークンIDを発行する関数。
元のトークンは関数を呼び出したアドレスの所有者となり、新たに`mint`（発行）されたトークンは指定されたアドレス（`to`）に送られます。

- `to`
    - 新しい所有者となるアドレス。
- `tokenIdToBeShared`
    - 共有・再発行される既存のトークンID。

戻り値として、新たに発行されたトークンIDを返します。
`Share`イベントは、`share`関数が呼び出され、指定されたトークンIDに基づく新しいトークンが生成され、アドレスに`transfer`（転送）されたときに発行されます。

# 補足

現在のNFT標準は譲渡可能な非代替性トークン（transferable non-fungible tokens）を定義していますが、共有可能な非代替性トークン（shareable non-fungible tokens）については定義していません。共有可能なNFTを作成するためには、既存のNFT契約を拡張し、共有の基本的な原則を定義するインターフェースを追加することが考えられます。具体的には、共有時のイベントと共有の関数メソッドを定義するインターフェースです。トークンの譲渡可能性に関する定義は、契約の実装者に委ねられます。譲渡が可能なままにされる場合、共有可能なトークンは既存のトークンと同様に動作しますが、共有されると元のトークンが保持されます。一方、譲渡が無効にされた場合、共有可能なトークンは共有可能な非譲渡性のトークンとなり、新しいトークンを発行して他の人に与えることはできますが、トークンを他の人に譲渡することはできません。

NFT（Non-Fungible Token）の標準では、通常は譲渡可能なトークンの定義がされています。
つまり、これらのトークンは所有者が他の人に譲渡することができます。
しかし、共有可能なNFTについては、既存のNFT標準では定義されていません。
共有可能なトークンを作成するためには、既存のNFTコントラクトを拡張し、共有の基本的な原則を定義する新しいインターフェースを追加する必要があります。

この新しいインターフェースでは、共有に関連するイベントや共有を行うための関数メソッドを定義します。
具体的には、共有時に発生するイベント（`Share`）を記録したり、共有の関数（`share`）呼び出して他の人とトークンを共有したりする方法を規定します。
トークンの譲渡可能性に関しては、コントラクトの実装者によって決定されます。
譲渡可能性が有効な場合、共有可能なトークンは通常のトークンと同じように他の人に譲渡することができますが、共有されると元のトークンは持ち主の手元に残ります。
一方、譲渡可能性が無効にされた場合、共有可能なトークンは共有可能な非譲渡性のトークンとなります。
これは、新しいトークンを発行して他の人に与えることはできますが、トークン自体は他の人に譲渡することはできません。

このように、共有可能なNFTを作成するには、既存のNFTコントラクトを拡張し、新しい共有のインターフェースを導入する必要があります。
共有可能性と譲渡可能性の設定によって、異なるタイプの共有可能なトークンを作成することが可能であり、トークンの持ち主が共有の方法を制御できるようになります。
これにより、より複雑な共有機能を実装したり、トークンの特性をより細かくカスタマイズしたりすることができます。

# シナリオ

例えば、**Bob**と**Alice**がプロジェクトで協力しています。
**Bob**はプロジェクトに対する貢献を示すユニークなNFTを獲得しますが、
**Bob**は自分の成果が自己の意思だけで達成されたものではないと感じています。
**Bob**は、彼女もプロジェクトに貢献した**Alice**と共有したいと考えています。
**Bob**は、彼のトークンを**Alice**と共有するために、自分のトークンを持つコントラクトで`share`関数を呼び出すことによって、どのトークンを共有したいか、誰に共有したいかを指示します。
**Alice**のために新しいトークンが作成されて`Share`イベントが発行され、共有されたことを伝えるためにトークンIDと共有元となった元のトークンIDが記録されます。

時間の経過とともに、`Share`イベント情報からツリー状の構造が形成されます。
**Bob**が**Alice**に共有し、**Alice**がさらに**Charlie**に共有し、**Alice**が**David**にも共有した場合、共有活動から基本的なツリー構造が形成されます。
これらの共有イベントデータは、トークンが表す共有活動に関する情報を後から利用することができます。

```
   B
   |
   A
  / \
 C   D

```
これらのツリー構造は、より大きなネットワーク表現に変換することも可能です。
例えば、**Bob**が**Alice**にトークンを共有し、**Alice**が別のトークンを**Charlie**に共有し、また**Bob**が**Charlie**にトークンを共有した場合、以下のようなネットワークが形成されます

```
B----A----C
\________/
```

# 後方互換性

この提案は**ERC721**と**ERC1155**と後方互換性があります。


# 参考実装
この参考実装は、特定のコミュニティ内で共有されるトークンの一般的なユースケースを示しています。
共有可能で譲渡不可能なトークンは、コントラクトの所有者がそのコミュニティに対して貢献した人々に対してメリットや功績を表彰するために使用されます。

具体的には、コントラクトの所有者は新しいトークンを作成して、そのトークンを特定の人に対して贈ることができます。
これは、その人がコミュニティに貢献したことや価値ある活動を行ったことを表彰するために行われます。
このトークンは、受け取った人がさらに他の人と共有することができます。
例えば、受け取ったメリットを、その人の貢献に参加したり影響を与えたりした他の人と共有することができます。

これにより、コミュニティ内での貢献や功績がトークンとして表現され、その価値が認識されることが可能となります。
また、トークンが共有されることによって、コミュニティ内のさまざまな関係者が相互につながり、共有された活動や影響のネットワークが形成されます。

例えば、Bobがコミュニティに貢献したときに、コントラクトの所有者から**Bob**に特別なトークンが与えられます。
**Bob**はこのトークンを受け取り、自分の貢献を評価してくれた他のメンバーや友人と共有することを決めます。
**Bob**がこのトークンを**Alice**と**Charlie**と共有することにしました。
**Alice**はさらに**Eve**と共有することを選択し、**Charlie**は**David**と共有することを選択しました。
このようにして、**Bob**のトークンはコミュニティ内の複数の人によって共有されることとなります。

これにより、**Bob**の貢献や功績がコミュニティ内で認識され、さらに共有されることで他の人にも影響を与える可能性があります。
また、共有されたトークンの情報から、トークンがどのような活動や関係性を表しているかを把握することができ、コミュニティの相互作用や連携を促進する手段として利用できるでしょう。

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

import "./IERC5023.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ShareableERC721 is ERC721URIStorage, Ownable, IERC5023 /* EIP165 */ {

  string baseURI;

  uint256 internal _currentIndex;
    
  constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}

  function mint(
        address account,
        uint256 tokenId
    ) external onlyOwner {
        _mint(account, tokenId);
  }

  function setTokenURI(
        uint256 tokenId, 
        string memory tokenURI
    ) external {
        _setTokenURI(tokenId, tokenURI);
  }

  function setBaseURI(string memory baseURI_) external {
        baseURI = baseURI_;
  }
    
  function _baseURI() internal view override returns (string memory) {
        return baseURI;
  }

  function share(address to, uint256 tokenIdToBeShared) external returns(uint256 newTokenId) {
      require(to != address(0), "ERC721: mint to the zero address");
      require(_exists(tokenIdToBeShared), "ShareableERC721: token to be shared must exist");
      
      require(msg.sender == ownerOf(tokenIdToBeShared), "Method caller must be the owner of token");

      string memory _tokenURI = tokenURI(tokenIdToBeShared);
      _mint(to, _currentIndex);
      _setTokenURI(_currentIndex, _tokenURI);

      emit Share(msg.sender, to, _currentIndex, tokenIdToBeShared);

      return _currentIndex;
  }

  function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        revert('In this reference implementation tokens are not transferrable');
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        revert('In this reference implementation tokens are not transferrable');
    }
}
```

上記のコードをいかにまとめています。

https://github.com/cardene777/base-code/tree/develop/contract/contracts/ERC5023

# テスト

テストコードは以下に格納されています。

https://github.com/cardene777/base-code/blob/develop/contract/test/ERC5023/ERC5023.test.ts

```sh
$ cd contract
$ npm install
$ npx hardhat test test/ERC6147/ERC6147.test.ts
```

上記コマンドでテストの実行ができます。

# セキュリティに関する考慮事項
参考実装をそのまま本番で使用すべきではありません。
この規格の実装に直接関係する他のセキュリティ上の考慮点は特にないです。

# 引用
Jarno Marttila (@yaruno), Martin Moravek (@mmartinmo), "ERC-5023: Shareable Non-Fungible Token," Ethereum Improvement Proposals, no. 5023, January 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5023.

# 考察
**ERC5023**を使用することで、NFTの所有権を保持したままコピーを他のアドレスに渡すことができます。
レンタルNFTの場合、NFT自体を他のアドレスに渡すため、所有者からすると若干の不安が残ります。
また、NFTの使用権を付与することも、なんらかの脆弱性がないか若干心配になります。
しかし、このコピーであればNFT自体は所有者管理になり、あくまでコピーを発行するためセキュリティ上安心できます。
ただ、実装を見るとコピーできる数はNFT1つにつき1つまでのようです（実装次第ではこの部分を変更できると思いますが）。
コピーしたNFTは譲渡可能/譲渡不可能にすることができます。
譲渡不可能にして、コピーしたアドレスのみ使用できるようにしても良いですし、譲渡可能にしてコピーしたNFTを使用しての売買などが起きても良さそうです。
コピーNFTで何か作ってみたいですね。

# 最後に

今回は「ERC721とERC20の機能を併せ持つ半代替性トークンと呼ばれている**ERC3525**」についてまとめてきました！
いかがだったでしょうか？

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

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

# 採用強化中！

CryptoGamesでは一緒に働く仲間を大募集中です。

https://www.wantedly.com/companies/cryptogames

この記事で書いた自分の経験からもわかるように、裁量権を持って働くことができて一気に成長できる環境です。
「ブロックチェーンやWeb3、NFTに興味がある」、「スマートコントラクトの開発に携わりたい」など、少しでも興味を持っている方はまずはお話ししましょう！