【Solidity】譲渡や二次流通ができないNFTの作り方
前置き
この記事は特定の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を継承している必要があります。
↑この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
と呼ばれるものがあります
// 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は専用マケプレでしか流通できないようにするロジックを定義しているようですが、これも少し応用すればできそうです。(代理コントラクト経由でしか操作を受け付けない等)
まとめ
-
_beforeTokenTransfer
メソッドをいじることでNFTの譲渡・二次流通制限をかけられる- 前提としてopenzeppelinのERC721の継承が必要
- 応用にPausableがある
- 譲渡制限されてること自体に価値が出てきてる・・・?価値というか意味?
EIP-1238
現時点2022/02/13ではまだ固まっていないが、ERC721を独自拡張するのではなく、譲渡不可なNFTの規格を定義しようというプロポーザルもあるようなので要チェックです
etc
Solidityについてワイワイ学ぶコミュニティ「solidity-jp」を作りました!
いまから学んでみたい/学習中だけどの日本語の情報が少ない/古くて時間がかかっているという方、一緒に学びましょう〜!!
また、TwitterにてSolidityに関する技術情報を発信しています。良ければフォローお願いします!
Discussion
初めまして。良質な記事をありがとうございます!
こちらを参考に譲渡に制限をかけたNFTを発行しようと考えています。
internal pure override
の部分で
TypeError: Function has override specified but does not override anything.
というエラーが起きます。
overrideするものがないという意味合いだと思いますがどのようにすれば解決されるでしょうか?
ご回答よろしくお願い致します。
ERC721の継承を忘れている・・・とかは流石にないですかね??
ここの部分です!
継承はされていると思うのですが...初学者に近いので不安です。
一度全て見ていただけますでしょうか?
先ほど申し上げていたoverrideのエラーは無くなったのですが、コンパイルしようとすると
と出てきます。
@nomiclabs/hardhat-etherscanは既にインストールしていて、nを取ってインストールしようとしても普通にエラーが起きます。
お時間頂戴して申し訳ありませんが解決策はございますでしょうか?