# 使用opensea伪造NFT

By [plsdm7](https://paragraph.com/@dogsuisui) · 2022-12-19

---

[https://app.pinata.cloud/pinmanager](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不可能会很快审查。其他平台暂时没发现这种

---

*Originally published on [plsdm7](https://paragraph.com/@dogsuisui/opensea-nft)*
