🔒

【Solidity】譲渡や二次流通ができないNFTの作り方

2022/02/01に公開約4,700字

前置き

この記事は特定のNFTや仮想通貨の購買を促進する記事ではありません。

概要

  • 譲渡(transfer)や二次流通(Opensea等)ができないNFTを作る
  • 譲渡制限、二次流通制限、転送制限と呼ばれているもの
  • NFTなのに譲渡や二次流通を制限したら意味ないじゃん・・・と思うかもしれませんが、一旦そこは置いといてください
  • 最近ではPOAP のような出席証明NFTも出てきていて、こちらは転送できないことに価値が生まれてるっぽい。

実装

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract SushiNeko is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;

    constructor() ERC721("SushiNeko", "NEKO") {}

    function mint() public {
        _tokenCounter.increment();

        uint256 newItemId = _tokenCounter.current();
        _mint(msg.sender, newItemId);
    }

    // ここを追加するだけ
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal pure override {
        // mintは許可(そのまま処理を通す)
	// transferは禁止(処理を中断させる)
        require(from == address(0));
    }
}

_beforeTokenTransfer?

なぜ _beforeTokenTransfer を実装すればよいだけなのか?

前提として openzeppelinのERC721を継承している必要があります。

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L280-L292

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L304-L318

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L331-L351

↑この3つのコードを見てみるとわかりますが、openzeppelinのERC721コントラクトの場合、どれもメソッド呼び出しの最初のほうで _beforeTokenTransfer が呼ばれていることがわかります。

    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");
        
	// ↓ここ↓
        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId);
    }

そもそも _beforeTokenTransfer のコメントを見るとわかりますが

@dev Hook that is called before any token transfer. This includes minting and burning.
トークン転送の前に呼び出されるフック。これには、mintとburningが含まれます。

NFTの mint, burn, transfer を実行する前に フックとして _beforeTokenTransfer メソッドを呼び出しています。

であれば、 _beforeTokenTransfer をoverrideし、mint時以外は失敗するようなコードを追加すればOKです。

// mintは許可(そのまま処理を通す)
// transferは禁止(処理を中断させる)
require(from == address(0));

本当にOpenSeaで取引できないのか?

先ほどのコントラクトをデプロイし、NFTをmintしてみました。
これをOpenSeaで売りに出してみます。

別ウォレットに切り替えて、購入ボタンを押してみます

Oops, the Ethereum network rejected this transaction :( The OpenSea devs have been alerted, but this problem is typically due an item being locked or untransferrable. The exact error was "execution reverted..
おっと、イーサリアムネットワークはこのトランザクションを拒否しました:( OpenSea開発者に警告がありましたが、この問題は通常、アイテムがロックされているか転送できないことが原因です。正確なエラーは「実行が元に戻されました。」でした。

エラーが出て購入ができないことがわかりました

応用

これを応用したものに Pausable と呼ばれるものがあります

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/Pausable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC721, Pausable, Ownable {
    constructor() ERC721("MyToken", "MTK") {}

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        whenNotPaused
        override
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }
}

コントラクトに何らかの脆弱性が見つかった場合や、連携するdAppsをメンテナンスモードにしている最中に(ゲームとか)二次流通を止めたい場合なんかに使います。

他にも、エウレカセブンのNFTは専用マケプレでしか流通できないようにするロジックを定義しているようですが、これも少し応用すればできそうです。(代理コントラクト経由でしか操作を受け付けない等)

https://twitter.com/DropMiin/status/1488320018693574656

まとめ

  • _beforeTokenTransfer メソッドをいじることでNFTの譲渡・二次流通制限をかけられる
    • 前提としてopenzeppelinのERC721の継承が必要
  • 応用にPausableがある
  • 譲渡制限されてること自体に価値が出てきてる・・・?価値というか意味?

EIP-1238

現時点2022/02/13ではまだ固まっていないが、ERC721を独自拡張するのではなく、譲渡不可なNFTの規格を定義しようというプロポーザルもあるようなので要チェックです

https://github.com/ethereum/EIPs/issues/1238

etc

Solidityについてワイワイ学ぶコミュニティ「solidity-jp」を作りました!
いまから学んでみたい/学習中だけどの日本語の情報が少ない/古くて時間がかかっているという方、一緒に学びましょう〜!!

https://discord.gg/8MsgnHCRud

また、TwitterにてSolidityに関する技術情報を発信しています。良ければフォローお願いします!

https://twitter.com/k0uhashi

Discussion

ログインするとコメントできます