https://app.pinata.cloud/pinmanager
使用openzepplin部署NFT合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts@4.5.0/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts@4.5.0/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts@4.5.0/access/Ownable.sol";
import "@openzeppelin/contracts@4.5.0/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts@4.5.0/utils/cryptography/ECDSA.sol";
contract WarriorERC721 is ERC721("Galxe Stargator Warrior NFTs", ""), ERC721Enumerable, Ownable, ReentrancyGuard {
string private _baseURIextended;
string private _baseURISuffix = "";
bool public frozenMetadata = false;
uint256 public MAX_TOKENS = 10000;
bool public saleIsActive = false;
bool public greenListSaleIsActive = false;
mapping(address => uint256) addressToGreenListNumberMinted;
mapping(address => uint256) addressToNumberMinted;
address private bouncer = 0xd47257ee6cD0a80Ed1ba7bDdC722Ef8eE44a8d7d;
bool private _baseUriIncludeTokenId = true;
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId);
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
return super.supportsInterface(interfaceId);
}
function flipSaleState() public onlyOwner {
saleIsActive = !saleIsActive;
}
function flipGreenListSaleState() public onlyOwner {
greenListSaleIsActive = !greenListSaleIsActive;
}
function setBaseURI(string memory baseURI_, bool includeTokenId, string memory suffix) external onlyOwner() {
require(!frozenMetadata, "The metadata URI is frozen. You can no longer set the baseURI");
_baseUriIncludeTokenId = includeTokenId;
_baseURIextended = baseURI_;
_baseURISuffix = suffix;
}
function _baseURI() internal view virtual override returns (string memory) {
return _baseURIextended;
}
function freezeMetadata() external onlyOwner {
frozenMetadata = true;
}
function showBaseURI() public view returns (string memory) {
return _baseURIextended;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI)) : "";
// if (_baseUriIncludeTokenId) {
// return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, Strings.toString(tokenId), _baseURISuffix)) : "";
// } else {
// return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI)) : "";
// }
}
function mintToken(uint numberOfTokens) public payable nonReentrant {
require(tx.origin == _msgSender(), "This function is only callable from an EOA.");
require(saleIsActive, "Sale must be active to mint Tokens");
require(numberOfTokens + addressToNumberMinted[_msgSender()] <= 10000, "Exceeded max token purchase");
require(totalSupply() + numberOfTokens <= MAX_TOKENS, "Purchase would exceed max supply of tokens");
require(0 * numberOfTokens <= msg.value, "Ether value sent is not correct");
for (uint i = 0; i < numberOfTokens; i++) {
uint mintIndex = totalSupply();
if (totalSupply() < MAX_TOKENS) {
addressToNumberMinted[_msgSender()] += 1;
_safeMint(_msgSender(), mintIndex);
}
}
}
function greenListMintToken(uint numberOfTokens, bytes memory signature) public payable nonReentrant {
require(tx.origin == _msgSender(), "This function is only callable from an EOA.");
require(greenListSaleIsActive, "Sale must be active to mint Tokens");
require(numberOfTokens + addressToGreenListNumberMinted[_msgSender()] <= 10000, "Exceeded max token purchase");
require(totalSupply() + numberOfTokens <= MAX_TOKENS, "Purchase would exceed max supply of tokens");
require(0 * numberOfTokens <= msg.value, "Ether value sent is not correct");
bytes32 hashedMinter = ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(_msgSender())));
address recoveredBouncer = ECDSA.recover(hashedMinter, signature);
require(recoveredBouncer == bouncer, "The signature for the greenlist is invalid");
for (uint i = 0; i < numberOfTokens; i++) {
uint mintIndex = totalSupply();
if (totalSupply() < MAX_TOKENS) {
addressToGreenListNumberMinted[_msgSender()] += 1;
_safeMint(_msgSender(), mintIndex);
}
}
}
function reserveTokens(uint quantity, address recipient) public onlyOwner {
uint supply = totalSupply();
require(supply + quantity <= MAX_TOKENS, "We have already hit the reserve limit");
uint i;
for (i = 0; i < quantity; i++) {
uint mintIndex = totalSupply();
_safeMint(recipient, mintIndex);
}
}
}
这个应该是quix launchpad的部分代码。
使用pinata生成ipfs,这里注意一下现在穿一个json里面配置好资源信息(重要的就是名字和图片地址了),如果所有图片都一样,那就传一个就可以了。如果每个需要单独配,可以看下合约代码,里面有关于index的部分。
对于所有NFT图片都一样的,可以如合约里直接写死baseURI,并且可以预留一个接口用来修改baseURI。
我开始还有个想法,就是用一个合约通过设置baseURI达到复用效果,但是这样的话需要把所有mint过的id都设置一遍,这个gas太高了所以放弃。
在市场上发布有几个注意点:
1、NFT的名字是唯一的,所以可以尽量写和原系列相似的
2、quix上可以添加表情符号,比如这个符号✅,懂得都懂
3、NFT类别可以多选几个,容易被搜索到
4、非常适合官方盲盒刚mint但是还不能开并且名字图片都一样的(因为id不一样配置文件要多个),基本做到一模一样。
5、成本就是部署合约(上面合约应该可以再简化例如全部写成onlyowner然后判断啥的都去了)和mint。现在polygon最合适的链,成本非常低可以忽略而且有很多热度项目。
6、OS上有很严格的审查程序,我试着改了几个热门项目的名字,然后修改元数据图片,没多久就被封号了。所以机会可能还是在新出的项目(比如刚mint的)这种OS不可能会很快审查。其他平台暂时没发现这种
