NFT

NFT盲盒开发

Posted on 2023-02-22

最近几年NFT火的一塌糊涂,各种数藏层出不穷。
多数都是采用了盲盒模式来玩,即购买的时候并不知道里面是什么,等到了一定的时间后,盲盒开启才会知道你买的 NFT 的具体属性,是否稀有等。
今天不讨论营销的模式,单纯从技术角度出发,如何制作出盲盒模式的NFT。

编写智能合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

/// @custom:security-contact bluequin@163.com
contract FdfNFT is ERC721, ERC721URIStorage, ERC721Burnable, AccessControl {
    using Counters for Counters.Counter;
    //是否开启盲盒
    bool private _blindBoxOpened = false;
    //设置盲盒uri
    string baseUri;
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    Counters.Counter private _tokenIdCounter;

    constructor() ERC721("FDF20220325FNFT", "FDF20220325FNFT") {
      _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
      _grantRole(MINTER_ROLE, msg.sender);
    }

    function setBaseUri(string memory _baseUri) public {

        require(
            hasRole(DEFAULT_ADMIN_ROLE, _msgSender()),
            "BaseERC721Uri: only admin can do this action"
        );
        baseUri = _baseUri;
    }

    function setBlindBoxOpened(bool _status) public {
        require(
            hasRole(DEFAULT_ADMIN_ROLE, _msgSender()),
            "BaseERC721BlindBox: only admin can do this action"
        );
        _blindBoxOpened = _status;
    }


    function safeMint(address to, string memory uri) public onlyRole(MINTER_ROLE) {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // The following functions are overrides required by Solidity.
    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }


    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        if(_blindBoxOpened){
            return super.tokenURI(tokenId);
        }else {
            return baseUri;
        }
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, AccessControl)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }

}

发布合约到 rinkeby 测试环境

这里直接用 remix 发布就行

在浏览器中查看合约部署情况。 这里看到合约已经创建成功,合约地址 0x5a501c079DB0BeB9A50aCA810be9d7277080E1B4

编写图片服务器接口

图片可以上传到 ipfs,但考虑到需要收额外的手续费,以及更加灵活的和前端调用,所以这里采用自己编写服务器接口
接口代码,这里使用 java 编写

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `image` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (1, '开启盲盒,单车变摩托', '开启盲盒,单车变摩托', 'https://lh3.googleusercontent.com/Op-cRm7h8p34PvxvzZbHh5nAmkS2rVZYil1lUYfyKrI4WuEOTG9X3_C1KvtlXGdyfwG_5wyfyOBwIytw9JZVcAkPtXq4PAdLOsmS=w340');
INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (2, '111', '朋克汽车', 'https://img01.jituwang.com/190326/256609-1Z326042P376.jpg');
INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (3, '222', '朋克', 'https://img01.jituwang.com/190330/256613-1Z33016444621.jpg');
INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (4, '333', '朋克', 'https://pic.ntimg.cn/file/20220107/32652940_141026506103_2.jpg');
INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (5, '444', '金克斯', 'https://pic.ntimg.cn/file/20220218/33805678_164928525106_2.jpg');
INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (6, '555', '源计划', 'https://img0.baidu.com/it/u=2501200624,302351145&fm=253&fmt=auto&app=138&f=PNG');
INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (7, '666', '源计划', 'https://img2.baidu.com/it/u=2016764601,1618302265&fm=253&fmt=auto&app=138&f=PNG');
INSERT INTO `test` (`id`, `name`, `description`, `image`) VALUES (8, '777', '未来战士', 'https://img0.baidu.com/it/u=718823970,501814686&fm=253&fmt=auto&app=138&f=PNG');

接口返回 json 结构数据。包含三个字段
image:图片真实地址
name:图片名称
description:图片描述

铸造 NFT

1.调用 setBaseUri 方法,设置 baseUri 为盲盒图片
2.调用 safeMint 方法铸造 nft


这里多创建几个

进入 opensea 测试网查看

https://testnets.opensea.io/

可以看到 nft 盲盒如下

开启盲盒

调用 setBlindBoxOpened 方法开启盲盒


refresh metadata

可以看到所有图片都已经展示出来了。(两张图片依然为盲盒,是因为我铸造 NFT 的时候,图片就是盲盒图片,尴尬一下 😓)