Nft智能合约发行,盲盒,公开发售技术实战–合约篇
// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; contract NftMeta is ERC721Enumerable, Ownable { using Strings for uint256; // 是否准许nft开卖-开关 bool public _isSaleActive = false; // 初始化盲盒,等到一定时机可以随机开箱,变成true bool public _revealed = false; // nft的总数量 uint256 public constant MAX_SUPPLY = 10; // 铸造Nft的价格 uint256 public mintPrice = 0.3 ether; // 铸造的钱包最多只能有一个nft数量 uint256 public maxBalance = 1; // 一次mint的nft的数量 uint256 public maxMint = 1; // 盲盒开关打开后,需要显示开箱的图片的base地址 string baseURI; // 盲盒图片的meta,json地址,后文会提到 string public notRevealedUri; // 默认地址的扩展类型 string public baseExtension = ".json"; mapping(uint256 => string) private _tokenURIs; // 构造器 constructor(string memory initBaseURI, string memory initNotRevealedUri) ERC721("Nft Meta", "NM") // 实现了ERC721的父类构造器,是子类继承的一种实现方式 { setBaseURI(initBaseURI); setNotRevealedURI(initNotRevealedUri); } // 外部地址进行铸造nft的函数调用 function mintNftMeta(uint256 tokenQuantity) public payable { // 校验总供应量+每次铸造的数量<= nft的总数量 require( totalSupply() + tokenQuantity <= MAX_SUPPLY, "Sale would exceed max supply" ); // 校验是否开启开卖状态 require(_isSaleActive, "Sale must be active to mint NicMetas"); // 校验铸造的钱包地址中的nft的数量 + 本次铸造的数量 <= 该钱包最大拥有的nft的数量 require( balanceOf(msg.sender) + tokenQuantity <= maxBalance, "Sale would exceed max balance" ); // 校验本次铸造的数量*铸造的价格 <= 本次消息附带的eth的数量 require( tokenQuantity * mintPrice <= msg.value, "Not enough ether sent" ); // 校验本次铸造的数量 <= 本次铸造的最大数量 require(tokenQuantity <= maxMint, "Can only mint 1 tokens at a time"); // 以上校验条件满足,进行nft的铸造 _mintNftMeta(tokenQuantity); } // 进行铸造 function _mintNftMeta(uint256 tokenQuantity) internal { for (uint256 i = 0; i < tokenQuantity; i++) { // mintIndex是铸造nft的序号,按照总供应量从0开始累加 uint256 mintIndex = totalSupply(); if (totalSupply() < MAX_SUPPLY) { // 调用erc721的安全铸造方法进行调用 _safeMint(msg.sender, mintIndex); } } } // 返回每个nft地址的Uri,这里包含了nft的整个信息,包括名字,描述,属性等 function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { require( _exists(tokenId), "ERC721Metadata: URI query for nonexistent token" ); // 盲盒还没开启,那么默认是一张黑色背景图片或者其他图片 if (_revealed == false) { return notRevealedUri; } string memory _tokenURI = _tokenURIs[tokenId]; string memory base = _baseURI(); // If there is no base URI, return the token URI. if (bytes(base).length == 0) { return _tokenURI; } // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). if (bytes(_tokenURI).length > 0) { return string(abi.encodePacked(base, _tokenURI)); } // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. return string(abi.encodePacked(base, tokenId.toString(), baseExtension)); } // internal function _baseURI() internal view virtual override returns (string memory) { return baseURI; } //only owner function flipSaleActive() public onlyOwner { _isSaleActive = !_isSaleActive; } function flipReveal() public onlyOwner { _revealed = !_revealed; } function setMintPrice(uint256 _mintPrice) public onlyOwner { mintPrice = _mintPrice; } function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner { notRevealedUri = _notRevealedURI; } function setBaseURI(string memory _newBaseURI) public onlyOwner { baseURI = _newBaseURI; } function setBaseExtension(string memory _newBaseExtension) public onlyOwner { baseExtension = _newBaseExtension; } function setMaxBalance(uint256 _maxBalance) public onlyOwner { maxBalance = _maxBalance; } function setMaxMint(uint256 _maxMint) public onlyOwner { maxMint = _maxMint; } function withdraw(address to) public onlyOwner { uint256 balance = address(this).balance; payable(to).transfer(balance); } }
过去的两年,关于NFT的话题一直很火热,随着NFT以各种形式出圈,在可见的未来,nft会以各种形式出圈并火爆。
这篇文章主要是从技术视角来给大家介绍一下nft主流的玩法-发行,盲盒,公开发售等流程步骤,这也是目前市场上大部分项目的玩法。
本次文章nft开发主要分为两部分: 1. 合约部分 2. 拼图部分。
本次Nft开发系列我也准备分为两篇文章进行讲解,今天主要讲解合约部分。
合约部分主要是以下功能:
建立和eth测试网互动的智能合约
Nft要能够被mint
Nft能够设定总量
Nft设定每个地址最大持有量
Nft要能够限定单次的mint的量
Nft要能够设定开关去公开发售
拼图部分主要是以下功能:
如何快速制作多种拼图以及meta资料
如何上传ipfs星际网络系统(测试网)
以上就是本次文章需要讲解的技术点。
以上就是合约源码,其实很简单,也不放在git上了,直接贴出来了。下面我就重点源码做一些解
对erc721的扩展部分和权限部分进行引用,同时进行继承,重写erc721的部分方法,下文会提到。
这些状态变量主要是用来对Nft的一些属性进行设置,包括总量,每个地址最多mint的数量等。
铸造方法实现
铸造方法实现
上面是铸造方法的实现,先进行铸造前的条件校验,然后调用erc721的_safemint安全方法进行铸造。
将合约代码进行部署,其中有几个注意的点
切换injected web3便于调用metamask插件,部署本次合约名称
切换injected web3便于调用metamask插件,部署本次合约名称
同时将测试网络切到Rinkeby测试网,同时需要提前通过faucet进行测试币的领取。领取地址:
Faucets | Chainlink
Faucets | Chainlink
Get testnet LINK to create and test your own Chainlinked smart contract on one of the supported blockchain testnets.
faucets.chain.link
FaucETH
FaucETH
Made with ❤️ by ligi | You can add funds to the faucet here | Request new networks via mail
fauceth.komputing.org
faucet.rinkeby.io
部署完成之后,会出现
在我们mint之前,需要进行开启铸造开关(红色框内),点击将状态变量变成true.
红色函数是铸造方法,填入数量1,同时因为我们在合约的状态变量设置了最小Mint的价格是0.3ether,因此我们需要在msg.value中附带ether数量从而完成mint。
因为红色框中是不允许输入小数的,因此,通过ether网站进行费用单位转换
eth-converter.com
将0.3ether输入,从而得到Gwei的数量,将该数量赋值到以上红框中进行mint。成功之后,就会在ipfs测试网看到一张编号为0的图片。ipfs测试网地址:
testnets.opensea.io
至此,我们nft开发中的合约部分已经完成。
https://mirror.xyz/0x6996B6Af0EC5d185408B42B8EA64b4ad09209414/IAusgeO6hJDHmyNxoGHfwigeVnS7nUIWgob7aYo-7bA