项目效果
创建并拥有一个自己的nft,而且我们可以改变其状态,从悲伤到开心,或者从开心到悲伤
合约地址:0xfc798485d88bdfB4448e397A827A39cdd4bfE380
概览
- 保存两种情绪图(s_happySvgUri、s_sadSvgUri),tokenURI 动态返回对应情绪的 base64 JSON(即 100% on-chain metadata)。
- 每 铸造 一个 token(id 从 0 递增),默认情绪是 HAPPY(枚举第一个值为 0)。
- 拥有者或被授权者可调用 flipMood(tokenId) 切换情绪。
nft创建者在初始化合约的时候将“开心”,“悲伤”两张图片传输到链上,后续用户在铸造nft的时候可以按照自己的意志切换nft的图片显示内容。
逻辑详解
enum NFTState { HAPPY, SAD }
uint256 private s_tokenCounter;
string private s_sadSvgUri;
string private s_happySvgUri;
mapping(uint256 => NFTState) private s_tokenIdToState;
AI写代码bash
12345
NFTState:枚举,默认值为 HAPPY(对应 0)。
s_tokenCounter:tokenId 计数器。
s_tokenIdToState:映射 tokenId → 当前情绪。未写入时返回默认 HAPPY。
constructor(string memory sadSvgUri, string memory happySvgUri) ERC721("Mood NFT", "MN") Ownable(msg.sender) {
s_tokenCounter = 0;
s_sadSvgUri = sadSvgUri;
s_happySvgUri = happySvgUri;
}
AI写代码bash
123456
构造函数,初始化图片信息以及铸造的代币的id
function mintNft() public {
uint256 tokenCounter = s_tokenCounter;
_safeMint(msg.sender, tokenCounter);
s_tokenCounter = s_tokenCounter + 1;
emit CreatedNFT(tokenCounter);
}
AI写代码bash
1234567
mintNft 公开可调用(任何人都能免费铸造),没有付款/数量限制/访问控制。
tokenCounter 用作新 tokenId,然后自增。
function flipMood(uint256 tokenId) public {
if (getApproved(tokenId) != msg.sender && ownerOf(tokenId) != msg.sender) {
revert MoodNft__CantFlipMoodIfNotOwner();
}
if (s_tokenIdToState[tokenId] == NFTState.HAPPY) {
s_tokenIdToState[tokenId] = NFTState.SAD;
} else {
s_tokenIdToState[tokenId] = NFTState.HAPPY;
}
}
AI写代码bash
123456789101112
只允许该nft的拥有者改变代币的状态
function _baseURI() internal pure override returns (string memory) {
return "data:application/json;base64,";
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (ownerOf(tokenId) == address(0)) {
revert ERC721Metadata__URI_QueryFor_NonExistentToken();
}
string memory imageURI = s_happySvgUri;
if (s_tokenIdToState[tokenId] == NFTState.SAD) {
imageURI = s_sadSvgUri;
}
return string(
abi.encodePacked(
_baseURI(),
Base64.encode(
bytes(abi.encodePacked(
'{"name":"', name(), '", "description":"An NFT that reflects the mood...", ',
'"attributes": [{"trait_type": "moodiness", "value": 100}], "image":"',
imageURI,
'"}'
))
)
)
);
}
AI写代码bash
123456789101112131415161718192021222324252627
_baseURI() 返回 data:application/json;base64,,所以 tokenURI() 返回一个完整的 data URI(metadata JSON 已 base64 编码)
imageURI 采用合约中保存的 s_happySvgUri/s_sadSvgUri
完整代码
pragma solidity 0.8.20;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
contract MoodNft is ERC721, Ownable {
error ERC721Metadata__URI_QueryFor_NonExistentToken();
error MoodNft__CantFlipMoodIfNotOwner();
enum NFTState {
HAPPY,
SAD
}
uint256 private s_tokenCounter;
string private s_sadSvgUri;
string private s_happySvgUri;
mapping(uint256 => NFTState) private s_tokenIdToState;
event CreatedNFT(uint256 indexed tokenId);
constructor(string memory sadSvgUri, string memory happySvgUri) ERC721("Mood NFT", "MN") Ownable(msg.sender) {
s_tokenCounter = 0;
s_sadSvgUri = sadSvgUri;
s_happySvgUri = happySvgUri;
}
function mintNft() public {
// how would you require payment for this NFT?
uint256 tokenCounter = s_tokenCounter;
_safeMint(msg.sender, tokenCounter);
s_tokenCounter = s_tokenCounter + 1;
emit CreatedNFT(tokenCounter);
}
function flipMood(uint256 tokenId) public {
if (getApproved(tokenId) != msg.sender && ownerOf(tokenId) != msg.sender) {
revert MoodNft__CantFlipMoodIfNotOwner();
}
if (s_tokenIdToState[tokenId] == NFTState.HAPPY) {
s_tokenIdToState[tokenId] = NFTState.SAD;
} else {
s_tokenIdToState[tokenId] = NFTState.HAPPY;
}
}
function _baseURI() internal pure override returns (string memory) {
return "data:application/json;base64,";
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (ownerOf(tokenId) == address(0)) {
revert ERC721Metadata__URI_QueryFor_NonExistentToken();
}
string memory imageURI = s_happySvgUri;
if (s_tokenIdToState[tokenId] == NFTState.SAD) {
imageURI = s_sadSvgUri;
}
return string(
abi.encodePacked(
_baseURI(),
Base64.encode(
bytes( // bytes casting actually unnecessary as 'abi.encodePacked()' returns a bytes
abi.encodePacked(
'{"name":"',
name(), // You can add whatever name here
'", "description":"An NFT that reflects the mood of the owner, 100% on Chain!", ',
'"attributes": [{"trait_type": "moodiness", "value": 100}], "image":"',
imageURI,
'"}'
)
)
)
)
);
}
function getHappySVG() public view returns (string memory) {
return s_happySvgUri;
}
function getSadSVG() public view returns (string memory) {
return s_sadSvgUri;
}
function getTokenCounter() public view returns (uint256) {
return s_tokenCounter;
}
}
AI写代码bash
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293