昨年(2022年)の下旬ごろから OpenSeaが NFTのクリエイター手数料に関する色々な提言をしています。で、すったもんだの結果「NFT作るなら EIP2981にご対応くださいましやがれコンチクショウ!」ということになった感じです。
https://twitter.com/opensea/status/1600913315576049669
今後新規の NFTを作る場合、ERC2981をセットで書くことになりそうなので、コピペ用にメモしておきます。
前提条件
環境は truffle、モジュールとして Openzeppelinを使うものとします。
コピペ手順
① インポート
なにはともあれ ERC2981をインポートします。
import "@openzeppelin/contracts/token/common/ERC2981.sol";
② 継承
スマートコントラクトの親クラスに ERC2981を追加してロイヤリティ機能を使えるようにします。
contract MyAwesomeToken is Ownable, ERC721, ERC2981 {
③ サポートインターフェイスのオーバーライド
サポートインターフェイスとは「あなたはこの機能を持っていますか?」とスマコン に問い合わせるための仕組みです。NFTであれば ERC721か ERC1155を継承しているはずでが、これらのクラスでは「私はNFTの機能を提供しますよ」という情報を返すために supportsInterfaceを実装しています。
一方で、ERC2981も「私はロイヤリティの機能を提供しますよ」というい情報を返すために supportsInterfaceを実装しています。
このように、多重継承した複数の親クラスが同じ名前の関数を実装している場合、サポートインターフェイスをオーバーライドして、曖昧さを解決する必要があります。
function supportsInterface(bytes4 interfaceId) public view override( ERC721, ERC2981 ) returns (bool) {
return super.supportsInterface(interfaceId);
}
上記では、override修飾子の引数に親クラスを並べることで、継承元のsupportsInterface関数の呼び出し順番を指定して曖昧さを解決しています(supportsInterfaceの場合はどのような順番で呼ばれても問題がないはずなので、親クラスの並び順は気にしなくてもOKです)。
ちなみに、オーバーライドしないと下記のようなコンパイルエラーがでるのでお気をつけください。
TypeError: Derived contract must override function "supportsInterface". Two or more base classes define function with same name and parameter types.
--> project:/contracts/Token.sol:14:1:
|
14 | contract MyAwesomeToken is Ownable, ERC721, ERC2981 {
| ^ (Relevant source part starts here and spans across multiple lines).
Note: Definition in "ERC721":
--> @openzeppelin/contracts/token/ERC721/ERC721.sol:52:5:
|
52 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
| ^ (Relevant source part starts here and spans across multiple lines).
Note: Definition in "ERC2981":
--> @openzeppelin/contracts/token/common/ERC2981.sol:36:5:
|
36 | function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
| ^ (Relevant source part starts here and spans across multiple lines).
Compilation failed. See above.
継承するクラスの構成によってエラーの内容は異なってくると思います。
ですが、もしエラーがでてもオーバーライドの対象となるクラス(ここでは ERC721と ERC2981)が列挙されるので、あわてずさわがず、supportsInterfaceの override修飾子に並べてエラーを修正してください。
④ デフォルト手数料の設定
ERC2981を継承しただけでは手数料の設定は空っぽです。
忘れないようにコンストラクタで設定しておきましょう。
constructor() Ownable() ERC721( "MyAwesomeNFT", "MANFT" ) {
/* 省略 */
// ロイヤリティは万分率で指定する(1000/10000 = 10%)
_setDefaultRoyalty( owner(), 1000 );
/* 省略 */
}
上の例では、「オーナー(ここではスマコンをデプロイしたアドレス)」に「10%」の手数料を指定しています。
⑤ 設定関数の窓口の用意
手数料の設定をリリース後に変更したくなるかもしれないので、各種設定関数の窓口を用意しておきましょう。
function setDefaultRoyalty( address receiver, uint96 feeNumerator ) external onlyOwner { _setDefaultRoyalty( receiver, feeNumerator ); }
function deleteDefaultRoyalty() external onlyOwner { _deleteDefaultRoyalty(); }
function setTokenRoyalty( uint256 tokenId, address receiver, uint96 feeNumerator ) external onlyOwner { _setTokenRoyalty( tokenId, receiver, feeNumerator ); }
function resetTokenRoyalty( uint256 tokenId ) external onlyOwner { _resetTokenRoyalty( tokenId ); }
上の例では ERC2981が用意しているロイヤリティ設定関数を外部へ公開しています(internalの関数を externalとして呼べるようにしています)。
この時、アクセス制限はお忘れなく。上記では、みんな大好き「onlyOwner」をつけて、オーナー以外が手数料の設定をできないようにしています。
以上です。
おわりに
5つのコピペで OpenSea対策は完了です。
もう少し凝ったことをしたい場合は「ERC2981.sol」のコードを読んで理解を深め、独自にカスタマイズしてくださいませ。