【Solidity】強制的に流動性を持たせる(かもしれない)NFTの作り方
前置き
この記事は特定のNFTや仮想通貨の購買を促進する記事ではありません。
概要
前回、【Solidity】譲渡や二次流通ができないNFTの作り方という記事を書きました。
では逆に、無理やり流動性を持たせるNFTを作る方法はないかな?と考えた結果、1つ面白いやり方が思いついたので記事化しました。
流動性を持たせる
「誰かに転送しないと一定期間後に売買も譲渡もburnもできないような仕組み」を作ることで流動性を無理やり持たせてみます。
今回のサンプル
- フルオンチェーンNFT。
- mintして誰かにNFTを転送する。
- 受取人は7日以内に誰かに渡さないと爆発し、以後売買も譲渡もできなくなる。
※NFTとして出すにはいろいろ欠陥があります
環境
- node v12.22.9
- hardhat 2.8.3
- macOS 12.0
開発
環境構築します
npm install -D hardhat
npx hardhat
npm install base64-sol @openzeppelin/contracts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "base64-sol/base64.sol";
contract TimeBomb is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private _tokenCounter;
mapping(uint256 => uint256) lastTransferredAt;
uint256 constant timeToExplosion = 7 days;
// uint256 constant timeToExplosion = 60 * 60 * 24 * 7; これと同じ
constructor() ERC721("TimeBomb", "BOMB") {}
function mintAndTransfer(address _to) public {
_tokenCounter.increment();
uint256 newItemId = _tokenCounter.current();
// 送付先アドレスにmintするのではなく、あえてmintを自身にする
// 誰がmintして送ってきたかがOpenSea上でわかりやすい。
_safeMint(msg.sender, newItemId);
_safeTransfer(msg.sender, _to, newItemId, "");
}
function tokenURI(uint256 tokenId)
public
view
override
returns (string memory)
{
require(
_exists(tokenId),
"ERC721Metadata: URI query for nonexistent token"
);
string memory svg = getSVG(tokenId);
bytes memory json = abi.encodePacked(
'{"name": "',
"Bomb! #",
Strings.toString(tokenId),
'", "image": "data:image/svg+xml;base64,',
Base64.encode(bytes(svg)),
'"}'
);
string memory _tokenURI = string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(json)
)
);
return _tokenURI;
}
// OpenSea等で表示される実体
function getSVG(uint256 _tokenId) private view returns (string memory) {
string memory bombText;
if (isExploded(lastTransferredAt[_tokenId], block.timestamp)) {
bombText = unicode"💥";
} else {
bombText = unicode"💣";
}
return
string(
abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350">',
'<rect width="100%" height="100%" fill="#eeeeee" />',
'<text text-anchor="middle" x="50%" y="60%" font-size="100px">',
bombText,
"</text>",
"</svg>"
)
);
}
// 爆発したかどうかチェック
// 最後に転送した時刻と現在時刻を元に計算し、爆発猶予時間を超えたか超えてないか?
function isExploded(uint256 _lastTransferredAt, uint256 _now)
public
pure
returns (bool)
{
return (_now - _lastTransferredAt) >= timeToExplosion;
}
// https://zenn.dev/ryo_takahashi/articles/d4bdc137b564db 参照
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override {
if (from != address(0)) {
// 爆発していたらtransferもburnも不可能
require(!isExploded(lastTransferredAt[tokenId], block.timestamp));
}
lastTransferredAt[tokenId] = block.timestamp;
super._beforeTokenTransfer(from, to, tokenId);
}
}
ポイントは 時刻計算
の部分と _beforeTokenTransfer内の譲渡制限
にあります。
7日後に爆発するといっても、受取時から7日後に爆発処理が行われるわけではなく、受取時に時刻を保存しておき、tokenURIやNFTの転送、売買を行うときにチェックするような仕組みです。
動作確認
テストネットワークにデプロイし、動作確認してみます。
mintやtransferはetherscanから叩いています。詳しくは以下。
サンプル↓
mintと同時にtransferできています。
これが7日後には以下のようになり、以降は転送も売買も不可能になります。
(メタデータを更新しなければ見た目が反映されないのが難しいところ)
昇華
このままだといろいろ破綻していますが、以下のようなアイデアを盛り込むと、もしかしたら価値が出てくるかも?
- 何回転送されたか加算して保持
- 爆発に近くなるほど状態が変化する
- だんだん爆弾が大きくなる
- 爆発まであと3日→背景オレンジ。あと1日→背景赤
なんか面白いアイデアがある人は教えてください 🙏
まとめ
- N日後に爆破する爆弾NFTを作りました
- なんか面白いNFTに化けるかもしれない(適当)
これ適当にNFTとして出したら、何十年後かに「数十年前から続いてる爆弾ゲームがあるらしいぞ!急げ〜〜💨💨」とかならないですかね?ならないか。
etc
Solidityについてワイワイ学ぶコミュニティ「solidity-jp」を作りました!
いまから学んでみたい/学習中だけどの日本語の情報が少ない/古くて時間がかかっているという方、一緒に学びましょう〜!!
また、TwitterにてSolidityに関する技術情報を発信しています。良ければフォローお願いします!
Discussion