区块链--表情变化的NFT

61 阅读4分钟

项目效果

创建并拥有一个自己的nft,而且我们可以改变其状态,从悲伤到开心,或者从开心到悲伤
合约地址:0xfc798485d88bdfB4448e397A827A39cdd4bfE380
在这里插入图片描述

概览

  1. 保存两种情绪图(s_happySvgUri、s_sadSvgUri),tokenURI 动态返回对应情绪的 base64 JSON(即 100% on-chain metadata)。
  2. 每 铸造 一个 token(id 从 0 递增),默认情绪是 HAPPY(枚举第一个值为 0)。
  3. 拥有者或被授权者可调用 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

项目链接

github.com/Cyfrin/foun…
github.com/Cyfrin/foun…